diff --git a/dev/local/docker-compose.yml b/dev/local/docker-compose.yml index 9418154b..da60baac 100644 --- a/dev/local/docker-compose.yml +++ b/dev/local/docker-compose.yml @@ -22,6 +22,16 @@ services: validation: image: ghcr.io/xmtp/mls-validation-service:main platform: linux/amd64 + environment: + ANVIL_URL: "http://anvil:8545" + + anvil: + image: ghcr.io/foundry-rs/foundry + platform: linux/amd64 + entrypoint: ["anvil", "--host", "0.0.0.0"] + working_dir: /anvil + ports: + - 8545:8545 db: image: postgres:13 diff --git a/library/build.gradle b/library/build.gradle index a7c2573c..886ffba8 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -86,16 +86,18 @@ dependencies { implementation 'io.grpc:grpc-okhttp:1.62.2' implementation 'io.grpc:grpc-protobuf-lite:1.62.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0' - implementation 'org.web3j:crypto:5.0.0' + implementation 'org.web3j:crypto:4.9.4' implementation "net.java.dev.jna:jna:5.14.0@aar" api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3' - api 'org.xmtp:proto-kotlin:3.62.1' + api 'org.xmtp:proto-kotlin:3.71.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'app.cash.turbine:turbine:1.1.0' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'org.web3j:core:4.9.4' + androidTestImplementation 'org.web3j:contracts:4.9.4' } afterEvaluate { diff --git a/library/src/androidTest/AndroidManifest.xml b/library/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..d3204757 --- /dev/null +++ b/library/src/androidTest/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt b/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt index a1c7655f..7696b4d9 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt @@ -159,7 +159,7 @@ class ClientTest { ) val inboxId = runBlocking { Client.getOrCreateInboxId(options, fakeWallet.address) } val client = runBlocking { - Client().createOrBuild( + Client().createV3( account = fakeWallet, options = options ) @@ -169,6 +169,18 @@ class ClientTest { } assert(client.installationId.isNotEmpty()) assertEquals(inboxId, client.inboxId) + + val sameClient = runBlocking { + Client().buildV3( + address = fakeWallet.address, + options = options + ) + } + runBlocking { + client.canMessageV3(listOf(sameClient.address))[sameClient.address]?.let { assert(it) } + } + assert(sameClient.installationId.isNotEmpty()) + assertEquals(client.inboxId, sameClient.inboxId) } @Test diff --git a/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt b/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt new file mode 100644 index 00000000..22ad96a2 --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt @@ -0,0 +1,379 @@ +package org.xmtp.android.library + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import app.cash.turbine.test +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.xmtp.android.library.codecs.ContentTypeReaction +import org.xmtp.android.library.codecs.Reaction +import org.xmtp.android.library.codecs.ReactionAction +import org.xmtp.android.library.codecs.ReactionCodec +import org.xmtp.android.library.codecs.ReactionSchema +import org.xmtp.android.library.messages.DecryptedMessage +import org.xmtp.android.library.messages.MessageDeliveryStatus +import org.xmtp.android.library.messages.PrivateKey +import org.xmtp.android.library.messages.PrivateKeyBuilder +import org.xmtp.android.library.messages.walletAddress +import java.security.SecureRandom + +@RunWith(AndroidJUnit4::class) +class DmTest { + private lateinit var alixWallet: PrivateKeyBuilder + private lateinit var boWallet: PrivateKeyBuilder + private lateinit var caroWallet: PrivateKeyBuilder + private lateinit var alix: PrivateKey + private lateinit var alixClient: Client + private lateinit var bo: PrivateKey + private lateinit var boClient: Client + private lateinit var caro: PrivateKey + private lateinit var caroClient: Client + + @Before + fun setUp() { + val key = SecureRandom().generateSeed(32) + val context = InstrumentationRegistry.getInstrumentation().targetContext + alixWallet = PrivateKeyBuilder() + alix = alixWallet.getPrivateKey() + alixClient = runBlocking { + Client().createV3( + account = alixWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + enableV3 = true, + appContext = context, + dbEncryptionKey = key + ) + ) + } + boWallet = PrivateKeyBuilder() + bo = boWallet.getPrivateKey() + boClient = runBlocking { + Client().createV3( + account = boWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + enableV3 = true, + appContext = context, + dbEncryptionKey = key + ) + ) + } + + caroWallet = PrivateKeyBuilder() + caro = caroWallet.getPrivateKey() + caroClient = runBlocking { + Client().createV3( + account = caroWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + enableV3 = true, + appContext = context, + dbEncryptionKey = key + ) + ) + } + } + + @Test + fun testCanCreateADm() { + runBlocking { + val convo1 = boClient.conversations.findOrCreateDm(alix.walletAddress) + alixClient.conversations.syncConversations() + val sameConvo1 = alixClient.conversations.findOrCreateDm(bo.walletAddress) + assertEquals(convo1.id, sameConvo1.id) + } + } + + @Test + fun testCanListDmMembers() { + val dm = runBlocking { + boClient.conversations.findOrCreateDm( + alix.walletAddress, + ) + } + assertEquals( + runBlocking { dm.members().map { it.inboxId }.sorted() }, + listOf( + alixClient.inboxId, + boClient.inboxId + ).sorted() + ) + + assertEquals( + runBlocking { + Conversation.Dm(dm).members().map { it.inboxId }.sorted() + }, + listOf( + alixClient.inboxId, + boClient.inboxId + ).sorted() + ) + + assertEquals( + runBlocking + { dm.peerInboxId() }, + alixClient.inboxId, + ) + } + + @Test + fun testDmMetadata() { + val boDm = runBlocking { + boClient.conversations.findOrCreateDm(alix.walletAddress) + } + + runBlocking { alixClient.conversations.syncConversations() } + val alixDm = runBlocking { alixClient.conversations.listDms().first() } + runBlocking { + alixDm.sync() + alixDm.updateName("This Is A Great Group") + alixDm.updateImageUrlSquare("thisisanewurl.com") + boDm.sync() + } + assertEquals("This Is A Great Group", boDm.name) + assertEquals("This Is A Great Group", alixDm.name) + assertEquals("thisisanewurl.com", boDm.imageUrlSquare) + assertEquals("thisisanewurl.com", alixDm.imageUrlSquare) + } + + @Test + fun testCannotCreateDmWithMemberNotOnV3() { + val chuxAccount = PrivateKeyBuilder() + val chux: PrivateKey = chuxAccount.getPrivateKey() + runBlocking { Client().create(account = chuxAccount) } + + assertThrows("Recipient not on network", XMTPException::class.java) { + runBlocking { boClient.conversations.findOrCreateDm(chux.walletAddress) } + } + } + + @Test + fun testCannotStartDmWithSelf() { + assertThrows("Recipient is sender", XMTPException::class.java) { + runBlocking { boClient.conversations.findOrCreateDm(bo.walletAddress) } + } + } + + @Test + fun testDmStartsWithAllowedState() { + runBlocking { + val dm = boClient.conversations.findOrCreateDm(alix.walletAddress) + dm.send("howdy") + dm.send("gm") + dm.sync() + assert(boClient.contacts.isGroupAllowed(dm.id)) + assertEquals(boClient.contacts.consentList.groupState(dm.id), ConsentState.ALLOWED) + assertEquals(dm.consentState(), ConsentState.ALLOWED) + } + } + + @Test + fun testCanSendMessageToDm() { + val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) } + runBlocking { dm.send("howdy") } + val messageId = runBlocking { dm.send("gm") } + runBlocking { dm.sync() } + assertEquals(dm.messages().first().body, "gm") + assertEquals(dm.messages().first().id, messageId) + assertEquals(dm.messages().first().deliveryStatus, MessageDeliveryStatus.PUBLISHED) + assertEquals(dm.messages().size, 3) + + runBlocking { alixClient.conversations.syncConversations() } + val sameDm = runBlocking { alixClient.conversations.listDms().last() } + runBlocking { sameDm.sync() } + assertEquals(sameDm.messages().size, 2) + assertEquals(sameDm.messages().first().body, "gm") + } + + @Test + fun testCanListDmMessages() { + val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) } + runBlocking { + dm.send("howdy") + dm.send("gm") + } + + assertEquals(dm.messages().size, 3) + assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 3) + runBlocking { dm.sync() } + assertEquals(dm.messages().size, 3) + assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.UNPUBLISHED).size, 0) + assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 3) + + runBlocking { alixClient.conversations.syncConversations() } + val sameDm = runBlocking { alixClient.conversations.listDms().last() } + runBlocking { sameDm.sync() } + assertEquals(sameDm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 2) + } + + @Test + fun testCanSendContentTypesToDm() { + Client.register(codec = ReactionCodec()) + + val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) } + runBlocking { dm.send("gm") } + runBlocking { dm.sync() } + val messageToReact = dm.messages()[0] + + val reaction = Reaction( + reference = messageToReact.id, + action = ReactionAction.Added, + content = "U+1F603", + schema = ReactionSchema.Unicode + ) + + runBlocking { + dm.send( + content = reaction, + options = SendOptions(contentType = ContentTypeReaction) + ) + } + runBlocking { dm.sync() } + + val messages = dm.messages() + assertEquals(messages.size, 3) + val content: Reaction? = messages.first().content() + assertEquals("U+1F603", content?.content) + assertEquals(messageToReact.id, content?.reference) + assertEquals(ReactionAction.Added, content?.action) + assertEquals(ReactionSchema.Unicode, content?.schema) + } + + @Test + fun testCanStreamDmMessages() = kotlinx.coroutines.test.runTest { + val group = boClient.conversations.findOrCreateDm(alix.walletAddress.lowercase()) + alixClient.conversations.syncConversations() + val alixDm = alixClient.findDm(bo.walletAddress) + group.streamMessages().test { + alixDm?.send("hi") + assertEquals("hi", awaitItem().body) + alixDm?.send("hi again") + assertEquals("hi again", awaitItem().body) + } + } + + @Test + fun testCanStreamAllMessages() { + val boDm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) } + runBlocking { alixClient.conversations.syncConversations() } + + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + alixClient.conversations.streamAllConversationMessages().collect { message -> + allMessages.add(message) + } + } catch (e: Exception) { + } + } + Thread.sleep(2500) + + for (i in 0 until 2) { + runBlocking { boDm.send(text = "Message $i") } + Thread.sleep(100) + } + assertEquals(2, allMessages.size) + + val caroDm = + runBlocking { caroClient.conversations.findOrCreateDm(alixClient.address) } + Thread.sleep(2500) + + for (i in 0 until 2) { + runBlocking { caroDm.send(text = "Message $i") } + Thread.sleep(100) + } + + assertEquals(4, allMessages.size) + + job.cancel() + } + + @Test + fun testCanStreamDecryptedDmMessages() = kotlinx.coroutines.test.runTest { + val dm = boClient.conversations.findOrCreateDm(alix.walletAddress) + alixClient.conversations.syncConversations() + val alixDm = alixClient.findDm(bo.walletAddress) + dm.streamDecryptedMessages().test { + alixDm?.send("hi") + assertEquals("hi", awaitItem().encodedContent.content.toStringUtf8()) + alixDm?.send("hi again") + assertEquals("hi again", awaitItem().encodedContent.content.toStringUtf8()) + } + } + + @Test + fun testCanStreamAllDecryptedDmMessages() { + val dm = runBlocking { boClient.conversations.findOrCreateDm(alix.walletAddress) } + runBlocking { alixClient.conversations.syncConversations() } + + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + alixClient.conversations.streamAllConversationDecryptedMessages().collect { message -> + allMessages.add(message) + } + } catch (e: Exception) { + } + } + Thread.sleep(2500) + + for (i in 0 until 2) { + runBlocking { dm.send(text = "Message $i") } + Thread.sleep(100) + } + assertEquals(2, allMessages.size) + + val caroDm = + runBlocking { caroClient.conversations.findOrCreateDm(alixClient.address) } + Thread.sleep(2500) + + for (i in 0 until 2) { + runBlocking { caroDm.send(text = "Message $i") } + Thread.sleep(100) + } + + assertEquals(4, allMessages.size) + + job.cancel() + } + + @Test + fun testCanStreamConversations() = kotlinx.coroutines.test.runTest { + boClient.conversations.streamConversations().test { + val dm = + alixClient.conversations.findOrCreateDm(bo.walletAddress) + assertEquals(dm.id, awaitItem().id) + val dm2 = + caroClient.conversations.findOrCreateDm(bo.walletAddress) + assertEquals(dm2.id, awaitItem().id) + } + } + + @Test + fun testDmConsent() { + runBlocking { + val dm = + boClient.conversations.findOrCreateDm(alix.walletAddress) + assert(boClient.contacts.isGroupAllowed(dm.id)) + assertEquals(dm.consentState(), ConsentState.ALLOWED) + + boClient.contacts.denyGroups(listOf(dm.id)) + assert(boClient.contacts.isGroupDenied(dm.id)) + assertEquals(dm.consentState(), ConsentState.DENIED) + + dm.updateConsentState(ConsentState.ALLOWED) + assert(boClient.contacts.isGroupAllowed(dm.id)) + assertEquals(dm.consentState(), ConsentState.ALLOWED) + } + } +} diff --git a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt index 58b6c542..a2eb832f 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt @@ -40,6 +40,9 @@ class GroupTest { private lateinit var caroWallet: PrivateKeyBuilder private lateinit var caro: PrivateKey private lateinit var caroClient: Client + private lateinit var davonV3Wallet: PrivateKeyBuilder + private lateinit var davonV3: PrivateKey + private lateinit var davonV3Client: Client private lateinit var fixtures: Fixtures @Before @@ -61,10 +64,13 @@ class GroupTest { bo = fixtures.bob caroWallet = fixtures.caroAccount caro = fixtures.caro + davonV3Wallet = fixtures.davonV3Account + davonV3 = fixtures.davonV3 alixClient = fixtures.aliceClient boClient = fixtures.bobClient caroClient = fixtures.caroClient + davonV3Client = fixtures.davonV3Client } @Test @@ -387,6 +393,8 @@ class GroupTest { runBlocking { boClient.conversations.newGroup(listOf(alix.walletAddress)) boClient.conversations.newGroup(listOf(caro.walletAddress)) + davonV3Client.conversations.findOrCreateDm(bo.walletAddress) + boClient.conversations.syncConversations() } val groups = runBlocking { boClient.conversations.listGroups() } assertEquals(groups.size, 2) @@ -398,6 +406,8 @@ class GroupTest { boClient.conversations.newGroup(listOf(alix.walletAddress)) boClient.conversations.newGroup(listOf(caro.walletAddress)) boClient.conversations.newConversation(alix.walletAddress) + davonV3Client.conversations.findOrCreateDm(bo.walletAddress) + boClient.conversations.syncConversations() } val convos = runBlocking { boClient.conversations.list(includeGroups = true) } assertEquals(convos.size, 3) @@ -589,6 +599,7 @@ class GroupTest { @Test fun testCanStreamAllGroupMessages() { val group = runBlocking { caroClient.conversations.newGroup(listOf(alix.walletAddress)) } + val dm = runBlocking { davonV3Client.conversations.findOrCreateDm(alix.walletAddress) } runBlocking { alixClient.conversations.syncGroups() } val allMessages = mutableListOf() @@ -603,8 +614,11 @@ class GroupTest { } Thread.sleep(2500) + runBlocking { dm.send("should not stream") } for (i in 0 until 2) { - runBlocking { group.send(text = "Message $i") } + runBlocking { + group.send(text = "Message $i") + } Thread.sleep(100) } assertEquals(2, allMessages.size) @@ -626,9 +640,10 @@ class GroupTest { @Test fun testCanStreamAllMessages() { val group = runBlocking { caroClient.conversations.newGroup(listOf(alix.walletAddress)) } + val dm = runBlocking { davonV3Client.conversations.findOrCreateDm(alix.walletAddress) } val conversation = runBlocking { boClient.conversations.newConversation(alix.walletAddress) } - runBlocking { alixClient.conversations.syncGroups() } + runBlocking { alixClient.conversations.syncConversations() } val allMessages = mutableListOf() @@ -646,6 +661,7 @@ class GroupTest { runBlocking { group.send("hi") conversation.send("hi") + dm.send("should not stream") } Thread.sleep(1000) @@ -671,7 +687,8 @@ class GroupTest { @Test fun testCanStreamAllDecryptedGroupMessages() { val group = runBlocking { caroClient.conversations.newGroup(listOf(alix.walletAddress)) } - runBlocking { alixClient.conversations.syncGroups() } + val dm = runBlocking { davonV3Client.conversations.findOrCreateDm(alix.walletAddress) } + runBlocking { alixClient.conversations.syncConversations() } val allMessages = mutableListOf() @@ -685,6 +702,7 @@ class GroupTest { } Thread.sleep(2500) + runBlocking { dm.send("Should not stream") } for (i in 0 until 2) { runBlocking { group.send(text = "Message $i") } Thread.sleep(100) @@ -708,6 +726,7 @@ class GroupTest { @Test fun testCanStreamAllDecryptedMessages() { val group = runBlocking { caroClient.conversations.newGroup(listOf(alix.walletAddress)) } + val dm = runBlocking { davonV3Client.conversations.findOrCreateDm(alix.walletAddress) } val conversation = runBlocking { boClient.conversations.newConversation(alix.walletAddress) } runBlocking { alixClient.conversations.syncGroups() } @@ -726,6 +745,7 @@ class GroupTest { Thread.sleep(2500) runBlocking { + dm.send("should not stream") group.send("hi") conversation.send("hi") } @@ -746,6 +766,9 @@ class GroupTest { val group2 = caroClient.conversations.newGroup(listOf(bo.walletAddress)) assertEquals(group2.id, awaitItem().id) + davonV3Client.conversations.findOrCreateDm(bo.walletAddress) + expectNoEvents() + cancelAndConsumeRemainingEvents() } } @@ -765,6 +788,7 @@ class GroupTest { Thread.sleep(2500) runBlocking { + davonV3Client.conversations.findOrCreateDm(alix.walletAddress) alixClient.conversations.newConversation(bo.walletAddress) Thread.sleep(2500) caroClient.conversations.newGroup(listOf(alix.walletAddress)) diff --git a/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt b/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt new file mode 100644 index 00000000..a65d3810 --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt @@ -0,0 +1,44 @@ +package org.xmtp.android.library + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SmartContractWalletTest { + @Test + fun testCanCreateASCW() { + val key = byteArrayOf( + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + ) + val context = InstrumentationRegistry.getInstrumentation().targetContext + val davonSCW = FakeSCWWallet.generate() + val options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + enableV3 = true, + appContext = context, + dbEncryptionKey = key + ) + val davonSCWClient = runBlocking { + Client().createV3( + account = davonSCW, + options = options + ) + } + val davonSCWClient2 = runBlocking { + Client().buildV3( + address = davonSCW.address, + chainId = davonSCW.chainId, + options = options + ) + } + + assertEquals(davonSCWClient.inboxId, davonSCWClient2.inboxId) + } +} diff --git a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt index 2542e543..cbcc2933 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt @@ -1,14 +1,27 @@ package org.xmtp.android.library import kotlinx.coroutines.runBlocking +import org.web3j.abi.FunctionEncoder +import org.web3j.abi.datatypes.DynamicBytes +import org.web3j.abi.datatypes.Uint +import org.web3j.crypto.Credentials +import org.web3j.crypto.Sign +import org.web3j.protocol.Web3j +import org.web3j.protocol.http.HttpService +import org.web3j.tx.gas.DefaultGasProvider +import org.web3j.utils.Numeric +import org.xmtp.android.library.artifact.CoinbaseSmartWallet +import org.xmtp.android.library.artifact.CoinbaseSmartWalletFactory import org.xmtp.android.library.messages.ContactBundle import org.xmtp.android.library.messages.Envelope import org.xmtp.android.library.messages.PrivateKey import org.xmtp.android.library.messages.PrivateKeyBuilder import org.xmtp.android.library.messages.Signature import org.xmtp.android.library.messages.Topic +import org.xmtp.android.library.messages.ethHash import org.xmtp.android.library.messages.toPublicKeyBundle import org.xmtp.android.library.messages.walletAddress +import java.math.BigInteger import java.util.Date class FakeWallet : SigningKey { @@ -41,6 +54,87 @@ class FakeWallet : SigningKey { get() = privateKey.walletAddress } +private const val ANVIL_TEST_PRIVATE_KEY = + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +private const val ANVIL_TEST_PORT = "http://10.0.2.2:8545" + +class FakeSCWWallet : SigningKey { + private var web3j: Web3j = Web3j.build(HttpService(ANVIL_TEST_PORT)) + private val contractDeployerCredentials: Credentials = + Credentials.create(ANVIL_TEST_PRIVATE_KEY) + var walletAddress: String = "" + + override val address: String + get() = walletAddress + + override val isSmartContractWallet: Boolean + get() = true + + override var chainId: Long? = 31337L + + companion object { + fun generate(): FakeSCWWallet { + return FakeSCWWallet().apply { + createSmartContractWallet() + } + } + } + + override suspend fun signSCW(message: String): ByteArray { + val smartWallet = CoinbaseSmartWallet.load( + walletAddress, + web3j, + contractDeployerCredentials, + DefaultGasProvider() + ) + val digest = Signature.newBuilder().build().ethHash(message) + val replaySafeHash = smartWallet.replaySafeHash(digest).send() + + val signature = + Sign.signMessage(replaySafeHash, contractDeployerCredentials.ecKeyPair, false) + val signatureBytes = signature.r + signature.s + signature.v + val tokens = listOf( + Uint(BigInteger.ZERO), + DynamicBytes(signatureBytes) + ) + val encoded = FunctionEncoder.encodeConstructor(tokens) + val encodedBytes = Numeric.hexStringToByteArray(encoded) + + return encodedBytes + } + + private fun createSmartContractWallet() { + val smartWalletContract = CoinbaseSmartWallet.deploy( + web3j, + contractDeployerCredentials, + DefaultGasProvider() + ).send() + + val factory = CoinbaseSmartWalletFactory.deploy( + web3j, + contractDeployerCredentials, + DefaultGasProvider(), + BigInteger.ZERO, + smartWalletContract.contractAddress + ).send() + + val ownerAddress = ByteArray(32) { 0 }.apply { + System.arraycopy(contractDeployerCredentials.address.hexToByteArray(), 0, this, 12, 20) + } + val owners = listOf(ownerAddress) + val nonce = BigInteger.ZERO + + val transactionReceipt = factory.createAccount(owners, nonce, BigInteger.ZERO).send() + val smartWalletAddress = factory.getAddress(owners, nonce).send() + + if (transactionReceipt.isStatusOK) { + walletAddress = smartWalletAddress + } else { + throw Exception("Transaction failed: ${transactionReceipt.status}") + } + } +} + data class Fixtures( val clientOptions: ClientOptions? = ClientOptions( ClientOptions.Api(XMTPEnvironment.LOCAL, isSecure = false) @@ -49,6 +143,7 @@ data class Fixtures( val aliceAccount = PrivateKeyBuilder() val bobAccount = PrivateKeyBuilder() val caroAccount = PrivateKeyBuilder() + val davonV3Account = PrivateKeyBuilder() var alice: PrivateKey = aliceAccount.getPrivateKey() var aliceClient: Client = @@ -62,6 +157,10 @@ data class Fixtures( var caroClient: Client = runBlocking { Client().create(account = caroAccount, options = clientOptions) } + var davonV3: PrivateKey = caroAccount.getPrivateKey() + var davonV3Client: Client = + runBlocking { Client().createV3(account = caroAccount, options = clientOptions) } + fun publishLegacyContact(client: Client) { val contactBundle = ContactBundle.newBuilder().also { builder -> builder.v1 = builder.v1.toBuilder().also { diff --git a/library/src/androidTest/java/org/xmtp/android/library/V3ClientTest.kt b/library/src/androidTest/java/org/xmtp/android/library/V3ClientTest.kt index f260d496..5ae75845 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/V3ClientTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/V3ClientTest.kt @@ -11,6 +11,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.xmtp.android.library.messages.DecryptedMessage import org.xmtp.android.library.messages.MessageDeliveryStatus import org.xmtp.android.library.messages.PrivateKey import org.xmtp.android.library.messages.PrivateKeyBuilder @@ -50,7 +51,7 @@ class V3ClientTest { boV3Wallet = PrivateKeyBuilder() boV3 = boV3Wallet.getPrivateKey() boV3Client = runBlocking { - Client().createOrBuild( + Client().createV3( account = boV3Wallet, options = ClientOptions( ClientOptions.Api(XMTPEnvironment.LOCAL, false), @@ -80,7 +81,8 @@ class V3ClientTest { @Test fun testsCanCreateGroup() { - val group = runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + val group = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } assertEquals( runBlocking { group.members().map { it.inboxId }.sorted() }, listOf(caroV2V3Client.inboxId, boV3Client.inboxId).sorted() @@ -92,8 +94,99 @@ class V3ClientTest { } @Test - fun testsCanSendMessages() { - val group = runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + fun testsCanCreateDm() { + val dm = runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + assertEquals( + runBlocking { dm.members().map { it.inboxId }.sorted() }, + listOf(caroV2V3Client.inboxId, boV3Client.inboxId).sorted() + ) + + val sameDm = runBlocking { boV3Client.findDm(caroV2V3.walletAddress) } + assertEquals(sameDm?.id, dm.id) + + runBlocking { caroV2V3Client.conversations.syncConversations() } + val caroDm = runBlocking { caroV2V3Client.findDm(boV3Client.address) } + assertEquals(caroDm?.id, dm.id) + + Assert.assertThrows("Recipient not on network", XMTPException::class.java) { + runBlocking { boV3Client.conversations.findOrCreateDm(alixV2.walletAddress) } + } + } + + @Test + fun testsCanFindConversationByTopic() { + val group = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + val dm = runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + + val sameDm = boV3Client.findConversationByTopic(dm.topic) + val sameGroup = boV3Client.findConversationByTopic(group.topic) + assertEquals(group.id, sameGroup?.id) + assertEquals(dm.id, sameDm?.id) + } + + @Test + fun testsCanListConversations() { + val dm = runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + val group = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + assertEquals(runBlocking { boV3Client.conversations.listConversations().size }, 2) + assertEquals(runBlocking { boV3Client.conversations.listDms().size }, 1) + assertEquals(runBlocking { boV3Client.conversations.listGroups().size }, 1) + + runBlocking { caroV2V3Client.conversations.syncConversations() } + assertEquals( + runBlocking { caroV2V3Client.conversations.list(includeGroups = true).size }, + 1 + ) + assertEquals(runBlocking { caroV2V3Client.conversations.listGroups().size }, 1) + } + + @Test + fun testsCanListConversationsFiltered() { + val dm = runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + val group = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + assertEquals(runBlocking { boV3Client.conversations.listConversations().size }, 2) + assertEquals( + runBlocking { boV3Client.conversations.listConversations(consentState = ConsentState.ALLOWED).size }, + 2 + ) + runBlocking { group.updateConsentState(ConsentState.DENIED) } + assertEquals( + runBlocking { boV3Client.conversations.listConversations(consentState = ConsentState.ALLOWED).size }, + 1 + ) + assertEquals( + runBlocking { boV3Client.conversations.listConversations(consentState = ConsentState.DENIED).size }, + 1 + ) + assertEquals(runBlocking { boV3Client.conversations.listConversations().size }, 2) + } + + @Test + fun testCanListConversationsOrder() { + val dm = runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + val group1 = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + val group2 = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + runBlocking { dm.send("Howdy") } + runBlocking { group2.send("Howdy") } + runBlocking { boV3Client.conversations.syncAllConversations() } + val conversations = runBlocking { boV3Client.conversations.listConversations() } + val conversationsOrdered = + runBlocking { boV3Client.conversations.listConversations(order = Conversations.ConversationOrder.LAST_MESSAGE) } + assertEquals(conversations.size, 3) + assertEquals(conversationsOrdered.size, 3) + assertEquals(conversations.map { it.id }, listOf(dm.id, group1.id, group2.id)) + assertEquals(conversationsOrdered.map { it.id }, listOf(group2.id, dm.id, group1.id)) + } + + @Test + fun testsCanSendMessagesToGroup() { + val group = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } runBlocking { group.send("howdy") } val messageId = runBlocking { group.send("gm") } runBlocking { group.sync() } @@ -102,13 +195,48 @@ class V3ClientTest { assertEquals(group.messages().first().deliveryStatus, MessageDeliveryStatus.PUBLISHED) assertEquals(group.messages().size, 3) - runBlocking { caroV2V3Client.conversations.syncGroups() } + runBlocking { caroV2V3Client.conversations.syncConversations() } val sameGroup = runBlocking { caroV2V3Client.conversations.listGroups().last() } runBlocking { sameGroup.sync() } assertEquals(sameGroup.messages().size, 2) assertEquals(sameGroup.messages().first().body, "gm") } + @Test + fun testsCanSendMessagesToDm() { + var boDm = + runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + runBlocking { boDm.send("howdy") } + var messageId = runBlocking { boDm.send("gm") } + var boDmMessage = runBlocking { boDm.messages() } + assertEquals(boDmMessage.first().body, "gm") + assertEquals(boDmMessage.first().id, messageId) + assertEquals(boDmMessage.first().deliveryStatus, MessageDeliveryStatus.PUBLISHED) + assertEquals(boDmMessage.size, 3) + + runBlocking { caroV2V3Client.conversations.syncConversations() } + val caroDm = runBlocking { caroV2V3Client.findDm(boV3.walletAddress) } + runBlocking { caroDm!!.sync() } + var caroDmMessage = runBlocking { caroDm!!.messages() } + assertEquals(caroDmMessage.size, 2) + assertEquals(caroDmMessage.first().body, "gm") + + runBlocking { caroDm!!.send("howdy") } + messageId = runBlocking { caroDm!!.send("gm") } + caroDmMessage = runBlocking { caroDm!!.messages() } + assertEquals(caroDmMessage.first().body, "gm") + assertEquals(caroDmMessage.first().id, messageId) + assertEquals(caroDmMessage.first().deliveryStatus, MessageDeliveryStatus.PUBLISHED) + assertEquals(caroDmMessage.size, 4) + + runBlocking { boV3Client.conversations.syncConversations() } + boDm = runBlocking { boV3Client.findDm(caroV2V3.walletAddress)!! } + runBlocking { boDm.sync() } + boDmMessage = runBlocking { boDm.messages() } + assertEquals(boDmMessage.size, 5) + assertEquals(boDmMessage.first().body, "gm") + } + @Test fun testGroupConsent() { runBlocking { @@ -156,12 +284,97 @@ class V3ClientTest { } } + @Test + fun testCanStreamAllMessagesFromV3Users() { + val group = + runBlocking { caroV2V3Client.conversations.newGroup(listOf(boV3.walletAddress)) } + val conversation = + runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + runBlocking { boV3Client.conversations.syncConversations() } + + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + boV3Client.conversations.streamAllConversationMessages() + .collect { message -> + allMessages.add(message) + } + } catch (e: Exception) { + } + } + Thread.sleep(1000) + runBlocking { + group.send("hi") + conversation.send("hi") + } + Thread.sleep(1000) + assertEquals(2, allMessages.size) + job.cancel() + } + + @Test + fun testCanStreamAllDecryptedMessagesFromV3Users() { + val group = + runBlocking { caroV2V3Client.conversations.newGroup(listOf(boV3.walletAddress)) } + val conversation = + runBlocking { boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) } + runBlocking { boV3Client.conversations.syncConversations() } + + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + boV3Client.conversations.streamAllConversationDecryptedMessages() + .collect { message -> + allMessages.add(message) + } + } catch (e: Exception) { + } + } + Thread.sleep(1000) + runBlocking { + group.send("hi") + conversation.send("hi") + } + Thread.sleep(1000) + assertEquals(2, allMessages.size) + job.cancel() + } + + @Test + fun testCanStreamGroupsAndConversationsFromV3Users() { + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + boV3Client.conversations.streamConversations() + .collect { message -> + allMessages.add(message.topic) + } + } catch (e: Exception) { + } + } + Thread.sleep(1000) + + runBlocking { + caroV2V3Client.conversations.newGroup(listOf(boV3.walletAddress)) + Thread.sleep(1000) + boV3Client.conversations.findOrCreateDm(caroV2V3.walletAddress) + } + + Thread.sleep(2000) + assertEquals(2, allMessages.size) + job.cancel() + } + @Test fun testCanStreamAllMessagesFromV2andV3Users() { - val group = runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } + val group = + runBlocking { boV3Client.conversations.newGroup(listOf(caroV2V3.walletAddress)) } val conversation = runBlocking { alixV2Client.conversations.newConversation(caroV2V3.walletAddress) } - runBlocking { caroV2V3Client.conversations.syncGroups() } + runBlocking { caroV2V3Client.conversations.syncConversations() } val allMessages = mutableListOf() diff --git a/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWallet.java b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWallet.java new file mode 100644 index 00000000..6a58dcd4 --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWallet.java @@ -0,0 +1,634 @@ +package org.xmtp.android.library.artifact; + +import io.reactivex.Flowable; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.web3j.abi.EventEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.abi.datatypes.DynamicStruct; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Utf8String; +import org.web3j.abi.datatypes.generated.Bytes1; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Bytes4; +import org.web3j.abi.datatypes.generated.Uint256; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.request.EthFilter; +import org.web3j.protocol.core.methods.response.BaseEventResponse; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tuples.generated.Tuple7; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 1.6.1. + */ +@SuppressWarnings("rawtypes") +public class CoinbaseSmartWallet extends Contract { + public static final String BINARY = "0x60a0604052306080523480156200001557600080fd5b50604080516001808252818301909252600091816020015b60608152602001906001900390816200002d57905050604080516000602082015291925001604051602081830303815290604052816000815181106200007757620000776200037a565b60209081029190910101526200008d8162000094565b50620005b0565b60005b81518110156200022657818181518110620000b657620000b66200037a565b602002602001015151602014158015620000ee5750818181518110620000e057620000e06200037a565b602002602001015151604014155b1562000136578181815181106200010957620001096200037a565b60200260200101516040516327755b9160e11b81526004016200012d9190620003b6565b60405180910390fd5b8181815181106200014b576200014b6200037a565b60200260200101515160201480156200019357506001600160a01b0380168282815181106200017e576200017e6200037a565b60200260200101516200019190620003eb565b115b15620001d257818181518110620001ae57620001ae6200037a565b602002602001015160405163bff1ac6560e01b81526004016200012d9190620003b6565b6200021d828281518110620001eb57620001eb6200037a565b6020026020010151620002036200022a60201b60201c565b8054906000620002138362000413565b909155506200023d565b60010162000097565b5050565b6000805160206200383783398151915290565b620002488262000326565b156200026b578160405163468b12ad60e11b81526004016200012d9190620003b6565b600160008051602062003837833981519152600201836040516200029091906200043b565b908152604051908190036020019020805491151560ff1990921691909117905581620002c86000805160206200383783398151915290565b60008381526001919091016020526040902090620002e79082620004e4565b50807f38109edc26e166b5579352ce56a50813177eb25208fd90d61f2f378386220220836040516200031a9190620003b6565b60405180910390a25050565b600060008051602062003837833981519152600201826040516200034b91906200043b565b9081526040519081900360200190205460ff1692915050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b60005b83811015620003ad57818101518382015260200162000393565b50506000910152565b6020815260008251806020840152620003d781604085016020870162000390565b601f01601f19169190910160400192915050565b805160208083015191908110156200040d576000198160200360031b1b821691505b50919050565b6000600182016200043457634e487b7160e01b600052601160045260246000fd5b5060010190565b600082516200044f81846020870162000390565b9190910192915050565b600181811c908216806200046e57607f821691505b6020821081036200040d57634e487b7160e01b600052602260045260246000fd5b601f821115620004df576000816000526020600020601f850160051c81016020861015620004ba5750805b601f850160051c820191505b81811015620004db57828155600101620004c6565b5050505b505050565b81516001600160401b0381111562000500576200050062000364565b620005188162000511845462000459565b846200048f565b602080601f831160018114620005505760008415620005375750858301515b600019600386901b1c1916600185901b178555620004db565b600085815260208120601f198616915b82811015620005815788860151825594840194600190910190840162000560565b5085821015620005a05787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b608051613264620005d36000396000818161081e015261095501526132646000f3fe60806040526004361061014f5760003560e01c806372de3b5a116100b6578063b0d691fe1161006f578063b0d691fe146103f4578063b61d27f61461041b578063bf6ba1fc1461042e578063ce1506be14610441578063d948fd2e14610461578063f698da251461048357610156565b806372de3b5a1461032957806384b0196e1461034957806388ce4c7c146103715780638ea69029146103875780639f9bcb34146103b4578063a2e1a8d8146103d457610156565b80633a871cdd116101085780633a871cdd146102655780634f1ef286146102865780634f6e7f221461029957806352d1902d146102b95780635c60da1b146102ce5780636f2de70e1461031657610156565b8063066a1eb7146101845780630f0f3f24146101b95780631626ba7e146101d95780631ca5393f1461021257806329565e3b1461023257806334fcd5be1461025257610156565b3661015657005b60003560e01c63bc197c81811463f23a6e6182141763150b7a028214171561018257806020526020603cf35b005b34801561019057600080fd5b506101a461019f366004612769565b610498565b60405190151581526020015b60405180910390f35b3480156101c557600080fd5b506101826101d43660046127a7565b610507565b3480156101e557600080fd5b506101f96101f436600461280a565b61053f565b6040516001600160e01b031990911681526020016101b0565b34801561021e57600080fd5b506101a461022d366004612940565b610579565b34801561023e57600080fd5b5061018261024d366004612769565b6105b4565b6101826102603660046129b8565b6105dd565b610278610273366004612a12565b6106e1565b6040519081526020016101b0565b610182610294366004612a5f565b61081c565b3480156102a557600080fd5b506102786102b4366004612a98565b610900565b3480156102c557600080fd5b50610278610951565b3480156102da57600080fd5b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b0390911681526020016101b0565b6101826103243660046129b8565b6109b1565b34801561033557600080fd5b50610182610344366004612acc565b6109f1565b34801561035557600080fd5b5061035e610ade565b6040516101b09796959493929190612b35565b34801561037d57600080fd5b5061027861210581565b34801561039357600080fd5b506103a76103a2366004612acc565b610b05565b6040516101b09190612bce565b3480156103c057600080fd5b506101a46103cf366004612be1565b610bc6565b3480156103e057600080fd5b506101a46103ef3660046127a7565b610c42565b34801561040057600080fd5b50735ff137d4b0fdcd49dca30c7cf57e578a026d27896102fe565b610182610429366004612c0b565b610c88565b61018261043c366004612c64565b610cec565b34801561044d57600080fd5b5061027861045c366004612acc565b610dad565b34801561046d57600080fd5b5060008051602061320f83398151915254610278565b34801561048f57600080fd5b50610278610db8565b60408051602081018490529081018290526000907f97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f029060600160408051601f19818403018152908290526104eb91612c99565b9081526040519081900360200190205460ff1690505b92915050565b61050f610e3e565b604080516001600160a01b038316602082015261053c91015b604051602081830303815290604052610e70565b50565b600061055461054d85610dad565b8484610e9b565b156105675750630b135d3f60e11b610572565b506001600160e01b03195b9392505050565b600060008051602061320f8339815191526002018260405161059b9190612c99565b9081526040519081900360200190205460ff1692915050565b6105bc610e3e565b60408051602081018490529081018290526105d990606001610528565b5050565b33735ff137d4b0fdcd49dca30c7cf57e578a026d27891461060057610600610e3e565b60005b818110156106dc576106d483838381811061062057610620612cb5565b90506020028101906106329190612ccb565b6106409060208101906127a7565b84848481811061065257610652612cb5565b90506020028101906106649190612ccb565b6020013585858581811061067a5761067a612cb5565b905060200281019061068c9190612ccb565b61069a906040810190612ce1565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fb092505050565b600101610603565b505050565b600033735ff137d4b0fdcd49dca30c7cf57e578a026d278914610716576040516282b42960e81b815260040160405180910390fd5b81602085013560401c600461072e6060880188612ce1565b90501015801561077257506107466060870187612ce1565b61075591600491600091612d27565b61075e91612d51565b6001600160e01b03191663bf6ba1fc60e01b145b156107b15761078086610900565b945061210581146107ac57604051632ef3781360e01b8152600481018290526024015b60405180910390fd5b6107d6565b61210581036107d657604051632ef3781360e01b8152600481018290526024016107a3565b6107ed856107e8610140890189612ce1565b610e9b565b156107fc576000925050610802565b60019250505b80156108145760003860003884335af1505b509392505050565b7f000000000000000000000000000000000000000000000000000000000000000030810361085257639f03a0266000526004601cfd5b61085b84611020565b8360601b60601c93506352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80602060016004601d895afa51146108ad576355299b496001526004601dfd5b847fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600038a284905581156108fa57604051828482376000388483885af46108f8573d6000823e3d81fd5b505b50505050565b600061090b82611028565b604080516020810192909252735ff137d4b0fdcd49dca30c7cf57e578a026d2789908201526060015b604051602081830303815290604052805190602001209050919050565b60007f000000000000000000000000000000000000000000000000000000000000000030811461098957639f03a0266000526004601cfd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc91505b5090565b60008051602061320f83398151915254156109df576040516302ed543d60e51b815260040160405180910390fd5b6105d96109ec8284612d81565b611041565b6109f9610e3e565b6000610a0482610b05565b90508051600003610a2b5760405163340c473d60e11b8152600481018390526024016107a3565b6040517f97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f0290610a5b908390612c99565b908152604051908190036020019020805460ff19169055610a8760008051602061320f83398151915290565b600083815260019190910160205260408120610aa29161271f565b817fcf95bbfe6f870f8cc40482dc3dccdafd268f0e9ce0a4f24ea1bea9be64e505ff82604051610ad29190612bce565b60405180910390a25050565b600f60f81b6060806000808083610af3611193565b97989097965046955030945091925090565b60008181527f97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f0160205260409020805460609190610b4190612e06565b80601f0160208091040260200160405190810160405280929190818152602001828054610b6d90612e06565b8015610bba5780601f10610b8f57610100808354040283529160200191610bba565b820191906000526020600020905b815481529060010190602001808311610b9d57829003601f168201915b50505050509050919050565b60006001600160e01b031982166329565e3b60e01b1480610bf757506001600160e01b031982166303c3cfc960e21b145b80610c1257506001600160e01b0319821663396f1dad60e11b145b80610c2d57506001600160e01b0319821663278f794360e11b145b15610c3a57506001919050565b506000919050565b600060008051602061320f833981519152604080516001600160a01b0385166020820152600292909201910160408051601f198184030181529082905261059b91612c99565b33735ff137d4b0fdcd49dca30c7cf57e578a026d278914610cab57610cab610e3e565b6108fa848484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fb092505050565b33735ff137d4b0fdcd49dca30c7cf57e578a026d278914610d1f576040516282b42960e81b815260040160405180910390fd5b6000610d2e6004828486612d27565b610d3791612d51565b9050610d4281610bc6565b610d6b57604051631d8370a360e11b81526001600160e01b0319821660048201526024016107a3565b6106dc30600085858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fb092505050565b6000610501826111da565b6000806000610dc5611193565b8151602080840191909120825182840120604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f9481019490945283019190915260608201524660808201523060a0820152919350915060c001604051602081830303815290604052805190602001209250505090565b610e4733610c42565b80610e5157503330145b15610e5857565b6040516282b42960e81b815260040160405180910390fd5b61053c8160008051602061320f8339815191525b8054906000610e9283612e50565b91905055611210565b600080610eaa83850185612e69565b90506000610ebb8260000151610b05565b90508051602003610f1a576001600160a01b03610ed782612ef5565b1115610ef8578060405163bff1ac6560e01b81526004016107a39190612bce565b600060208201519050610f10818885602001516112df565b9350505050610572565b8051604003610f955760008082806020019051810190610f3a9190612f19565b9150915060008460200151806020019051810190610f589190612f82565b9050610f8989604051602001610f7091815260200190565b60405160208183030381529060405260008386866113e4565b95505050505050610572565b806040516327755b9160e11b81526004016107a39190612bce565b600080846001600160a01b03168484604051610fcc9190612c99565b60006040518083038185875af1925050503d8060008114611009576040519150601f19603f3d011682016040523d82523d6000602084013e61100e565b606091505b5091509150816108f857805160208201fd5b61053c610e3e565b600061103382611754565b805190602001209050919050565b60005b81518110156105d95781818151811061105f5761105f612cb5565b602002602001015151602014158015611093575081818151811061108557611085612cb5565b602002602001015151604014155b156110cc578181815181106110aa576110aa612cb5565b60200260200101516040516327755b9160e11b81526004016107a39190612bce565b8181815181106110de576110de612cb5565b602002602001015151602014801561112057506001600160a01b03801682828151811061110d5761110d612cb5565b602002602001015161111e90612ef5565b115b156111595781818151811061113757611137612cb5565b602002602001015160405163bff1ac6560e01b81526004016107a39190612bce565b61118b82828151811061116e5761116e612cb5565b6020026020010151610e8460008051602061320f83398151915290565b600101611044565b604080518082018252601581527410dbda5b98985cd94814db585c9d0815d85b1b195d605a1b602080830191909152825180840190935260018352603160f81b9083015291565b60006111e4610db8565b6111ed83611827565b60405161190160f01b602082015260228101929092526042820152606201610934565b61121982610579565b15611239578160405163468b12ad60e11b81526004016107a39190612bce565b600160008051602061320f8339815191526002018360405161125b9190612c99565b908152604051908190036020019020805491151560ff199092169190911790558161129160008051602061320f83398151915290565b600083815260019190910160205260409020906112ae908261308d565b50807f38109edc26e166b5579352ce56a50813177eb25208fd90d61f2f37838622022083604051610ad29190612bce565b6001600160a01b03909216916000831561057257604051836000526020830151604052604083510361134f576040830151601b8160ff1c016020528060011b60011c60605250602060016080600060015afa805186183d151761134d57506000606052604052506001610572565b505b604183510361139557606083015160001a6020526040830151606052602060016080600060015afa805186183d151761139357506000606052604052506001610572565b505b600060605280604052631626ba7e60e01b808252846004830152602482016040815284516020018060448501828860045afa505060208160443d01858a5afa9051909114169150509392505050565b60007f7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a88460a00151111561141a5750600061174b565b606084015160009061143d9061143181601561314c565b60208801519190611862565b90507fff1a2a9176d650e4a99dedb58f1793003935130579fe17b5a3f698ac5b00e63481805190602001201461147757600091505061174b565b6000611485886001806118c8565b604051602001611495919061315f565b604051602081830303815290604052905060006114cd8760400151835189604001516114c1919061314c565b60208a01519190611862565b905081805190602001208180519060200120146114f0576000935050505061174b565b86518051600160f81b918291602090811061150d5761150d612cb5565b0160200151166001600160f81b0319161461152e576000935050505061174b565b878015611566575086518051600160fa1b918291602090811061155357611553612cb5565b0160200151166001600160f81b03191614155b15611577576000935050505061174b565b60006002886020015160405161158d9190612c99565b602060405180830381855afa1580156115aa573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906115cd91906131a0565b9050600060028960000151836040516020016115ea9291906131b9565b60408051601f198184030181529082905261160491612c99565b602060405180830381855afa158015611621573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061164491906131a0565b6080808b015160a0808d015160408051602081018790529081019390935260608301529181018b905290810189905290915060009060c00160405160208183030381529060405290506000806101006001600160a01b0316836040516116aa9190612c99565b600060405180830381855afa9150503d80600081146116e5576040519150601f19603f3d011682016040523d82523d6000602084013e6116ea565b606091505b508051919350915015158280156116fe5750805b1561172a578180602001905181019061171791906131a0565b600114995050505050505050505061174b565b61173f858e608001518f60a001518f8f6119bd565b99505050505050505050505b95945050505050565b606081356020830135600061177461176f6040870187612ce1565b611aa0565b9050600061178861176f6060880188612ce1565b9050608086013560a087013560c088013560e08901356101008a013560006117b761176f6101208e018e612ce1565b604080516001600160a01b039c909c1660208d01528b81019a909a5260608b019890985250608089019590955260a088019390935260c087019190915260e08601526101008501526101208401526101408084019190915281518084039091018152610160909201905292915050565b604080517f9b493d222105fee7df163ab5d57f0bf1ffd2da04dd5fafbe10b54c41c1adc6576020820152908101829052600090606001610934565b60608351828111611871578092505b83811161187c578093505b5081831015610572575060405182820380825293830193601f19601f820181165b868101518482015281018061189d5750600083830160200152603f9091011681016040529392505050565b606083518015610814576003600282010460021b60405192507f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f526106708515027f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f18603f52602083018181018388602001018051600082525b60038a0199508951603f8160121c1651600053603f81600c1c1651600153603f8160061c1651600253603f811651600353506000518452600484019350828410611944579052602001604052613d3d60f01b60038406600204808303919091526000861515909102918290035290038252509392505050565b60008415806119da57506000805160206131ef8339815191528510155b806119e3575083155b806119fc57506000805160206131ef8339815191528410155b15611a095750600061174b565b611a138383611ab3565b611a1f5750600061174b565b6000611a2a85611bad565b905060006000805160206131ef833981519152828909905060006000805160206131ef83398151915283890990506000611a6687878585611c1f565b90506000805160206131ef833981519152611a8f8a6000805160206131ef8339815191526131db565b8208159a9950505050505050505050565b6000604051828085833790209392505050565b600082158015611ac1575081155b80611ad95750600160601b63ffffffff60c01b031983145b80611af15750600160601b63ffffffff60c01b031982145b15611afe57506000610501565b6000600160601b63ffffffff60c01b031983840990506000600160601b63ffffffff60c01b0319807fffffffff00000001000000000000000000000000fffffffffffffffffffffffc8709600160601b63ffffffff60c01b031987600160601b63ffffffff60c01b0319898a0909089050600160601b63ffffffff60c01b03197f5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b820891909114949350505050565b600060405160208152602080820152602060408201528260608201527fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f60808201526000805160206131ef83398151915260a082015260208160c0836005600019fa611c1857600080fd5b5192915050565b600080808060ff818088158015611c34575087155b15611c4857600096505050505050506122e1565b611c947f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2967f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f58d8d6122e9565b909250905081158015611ca5575080155b15611cd3576000805160206131ef833981519152886000805160206131ef833981519152038a089850600097505b600189841c16600189851c1660011b015b80611d065760018403935060018a851c1660018a861c1660011b019050611ce4565b50600189841c16600189851c1660011b01955060018603611d68577f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29696507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f593505b60028603611d77578a96508993505b60038603611d86578196508093505b60018303925060019550600194505b82600019111561226a57600160601b63ffffffff60c01b031984600209600160601b63ffffffff60c01b0319818209600160601b63ffffffff60c01b0319818a09600160601b63ffffffff60c01b03198284099250600160601b63ffffffff60c01b031980600160601b63ffffffff60c01b03198b8d08600160601b63ffffffff60c01b03198c600160601b63ffffffff60c01b0319038e0809600309600160601b63ffffffff60c01b03198985099850600160601b63ffffffff60c01b03198a84099950600160601b63ffffffff60c01b031980836002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b0319838409089a50600160601b63ffffffff60c01b03198083600160601b63ffffffff60c01b0319038d0882099250600160601b63ffffffff60c01b031983600160601b63ffffffff60c01b03198a870908975060018d881c1660018d891c1660011b01905080611f125787600160601b63ffffffff60c01b03190397505050505061225f565b60018103611f61577f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29693507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f592505b60028103611f70578e93508d92505b60038103611f7f578593508492505b89611f98575091985060019750879650945061225f9050565b600160601b63ffffffff60c01b031988600160601b63ffffffff60c01b03198b860908600160601b63ffffffff60c01b03198c600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b03198d880908935080612151578361215157600160601b63ffffffff60c01b0319896002600160601b0363ffffffff60c01b0319099450600160601b63ffffffff60c01b03198586099350600160601b63ffffffff60c01b0319848d099250600160601b63ffffffff60c01b03198486099450600160601b63ffffffff60c01b0319808c600160601b63ffffffff60c01b0319038e08600160601b63ffffffff60c01b03198d8f08099050600160601b63ffffffff60c01b0319816003099150600160601b63ffffffff60c01b03198a86099950600160601b63ffffffff60c01b03198b85099a50600160601b63ffffffff60c01b031980846002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b0319848509089b50600160601b63ffffffff60c01b0319808d600160601b63ffffffff60c01b031903850883099350600160601b63ffffffff60c01b0319808a870985089850505050505061225f565b600160601b63ffffffff60c01b03198485099150600160601b63ffffffff60c01b0319848309600160601b63ffffffff60c01b0319838d099b50600160601b63ffffffff60c01b0319818c099a50600160601b63ffffffff60c01b0319838e09600160601b63ffffffff60c01b031980826002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b031984600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b031987880908089350600160601b63ffffffff60c01b031980838d09600160601b63ffffffff60c01b031985600160601b63ffffffff60c01b031988600160601b63ffffffff60c01b031903860809089a50505050809a50505050505b600183039250611d95565b60405186606082015260208152602080820152602060408201526002600160601b0363ffffffff60c01b03196080820152600160601b63ffffffff60c01b031960a082015260208160c0836005600019fa6122c457600080fd5b600160601b63ffffffff60c01b0319815189099750505050505050505b949350505050565b60008080808661230057858593509350505061236e565b8461231257878793509350505061236e565b858814801561232057508487145b15612341576123328888600180612377565b929a509098509250905061235b565b61235088886001808a8a6124d2565b929a50909850925090505b61236788888484612656565b9350935050505b94509492505050565b600080600080600160601b63ffffffff60c01b0319876002099350600160601b63ffffffff60c01b03198485099150600160601b63ffffffff60c01b03198289099050600160601b63ffffffff60c01b03198285099250600160601b63ffffffff60c01b03198683099150600160601b63ffffffff60c01b031980600160601b63ffffffff60c01b0319888b08600160601b63ffffffff60c01b031989600160601b63ffffffff60c01b0319038c08096003099550600160601b63ffffffff60c01b031980826002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b0319888909089350600160601b63ffffffff60c01b03198085600160601b63ffffffff60c01b031903830887099750600160601b63ffffffff60c01b03198584099050600160601b63ffffffff60c01b031980888509600160601b63ffffffff60c01b03190389089250945094509450949050565b600080600080886000036124f157508492508391506001905080612649565b600160601b63ffffffff60c01b0319988903988981898809089450600160601b63ffffffff60c01b03198a600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b03198a8909089550600160601b63ffffffff60c01b03198687099350600160601b63ffffffff60c01b03198685099250600160601b63ffffffff60c01b03198489099150600160601b63ffffffff60c01b03198388099050600160601b63ffffffff60c01b0319848b099750600160601b63ffffffff60c01b031980896002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b031985600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b0319898a0908089350600160601b63ffffffff60c01b031980848b09600160601b63ffffffff60c01b031987600160601b63ffffffff60c01b031988600160601b63ffffffff60c01b0319038d08090892505b9650965096509692505050565b6000806000612664846126c3565b9050600160601b63ffffffff60c01b031981870991506000600160601b63ffffffff60c01b03198287099050600160601b63ffffffff60c01b03198182099150600160601b63ffffffff60c01b03198289099350505094509492505050565b600060405160208152602080820152602060408201528260608201526002600160601b0363ffffffff60c01b03196080820152600160601b63ffffffff60c01b031960a082015260208160c0836005600019fa611c1857600080fd5b50805461272b90612e06565b6000825580601f1061273b575050565b601f01602090049060005260206000209081019061053c91905b808211156109ad5760008155600101612755565b6000806040838503121561277c57600080fd5b50508035926020909101359150565b80356001600160a01b03811681146127a257600080fd5b919050565b6000602082840312156127b957600080fd5b6105728261278b565b60008083601f8401126127d457600080fd5b5081356001600160401b038111156127eb57600080fd5b60208301915083602082850101111561280357600080fd5b9250929050565b60008060006040848603121561281f57600080fd5b8335925060208401356001600160401b0381111561283c57600080fd5b612848868287016127c2565b9497909650939450505050565b634e487b7160e01b600052604160045260246000fd5b60405160c081016001600160401b038111828210171561288d5761288d612855565b60405290565b604051601f8201601f191681016001600160401b03811182821017156128bb576128bb612855565b604052919050565b60006001600160401b038211156128dc576128dc612855565b50601f01601f191660200190565b600082601f8301126128fb57600080fd5b813561290e612909826128c3565b612893565b81815284602083860101111561292357600080fd5b816020850160208301376000918101602001919091529392505050565b60006020828403121561295257600080fd5b81356001600160401b0381111561296857600080fd5b6122e1848285016128ea565b60008083601f84011261298657600080fd5b5081356001600160401b0381111561299d57600080fd5b6020830191508360208260051b850101111561280357600080fd5b600080602083850312156129cb57600080fd5b82356001600160401b038111156129e157600080fd5b6129ed85828601612974565b90969095509350505050565b60006101608284031215612a0c57600080fd5b50919050565b600080600060608486031215612a2757600080fd5b83356001600160401b03811115612a3d57600080fd5b612a49868287016129f9565b9660208601359650604090950135949350505050565b600080600060408486031215612a7457600080fd5b612a7d8461278b565b925060208401356001600160401b0381111561283c57600080fd5b600060208284031215612aaa57600080fd5b81356001600160401b03811115612ac057600080fd5b6122e1848285016129f9565b600060208284031215612ade57600080fd5b5035919050565b60005b83811015612b00578181015183820152602001612ae8565b50506000910152565b60008151808452612b21816020860160208601612ae5565b601f01601f19169290920160200192915050565b60ff60f81b881681526000602060e06020840152612b5660e084018a612b09565b8381036040850152612b68818a612b09565b606085018990526001600160a01b038816608086015260a0850187905284810360c08601528551808252602080880193509091019060005b81811015612bbc57835183529284019291840191600101612ba0565b50909c9b505050505050505050505050565b6020815260006105726020830184612b09565b600060208284031215612bf357600080fd5b81356001600160e01b03198116811461057257600080fd5b60008060008060608587031215612c2157600080fd5b612c2a8561278b565b93506020850135925060408501356001600160401b03811115612c4c57600080fd5b612c58878288016127c2565b95989497509550505050565b60008060208385031215612c7757600080fd5b82356001600160401b03811115612c8d57600080fd5b6129ed858286016127c2565b60008251612cab818460208701612ae5565b9190910192915050565b634e487b7160e01b600052603260045260246000fd5b60008235605e19833603018112612cab57600080fd5b6000808335601e19843603018112612cf857600080fd5b8301803591506001600160401b03821115612d1257600080fd5b60200191503681900382131561280357600080fd5b60008085851115612d3757600080fd5b83861115612d4457600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015612d795780818660040360031b1b83161692505b505092915050565b60006001600160401b0380841115612d9b57612d9b612855565b8360051b6020612dad60208301612893565b86815291850191602081019036841115612dc657600080fd5b865b84811015612dfa57803586811115612de05760008081fd5b612dec36828b016128ea565b845250918301918301612dc8565b50979650505050505050565b600181811c90821680612e1a57607f821691505b602082108103612a0c57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600060018201612e6257612e62612e3a565b5060010190565b600060208284031215612e7b57600080fd5b81356001600160401b0380821115612e9257600080fd5b9083019060408286031215612ea657600080fd5b604051604081018181108382111715612ec157612ec1612855565b60405282358152602083013582811115612eda57600080fd5b612ee6878286016128ea565b60208301525095945050505050565b80516020808301519190811015612a0c5760001960209190910360031b1b16919050565b60008060408385031215612f2c57600080fd5b505080516020909101519092909150565b600082601f830112612f4e57600080fd5b8151612f5c612909826128c3565b818152846020838601011115612f7157600080fd5b6122e1826020830160208701612ae5565b600060208284031215612f9457600080fd5b81516001600160401b0380821115612fab57600080fd5b9083019060c08286031215612fbf57600080fd5b612fc761286b565b825182811115612fd657600080fd5b612fe287828601612f3d565b825250602083015182811115612ff757600080fd5b61300387828601612f3d565b60208301525060408301516040820152606083015160608201526080830151608082015260a083015160a082015280935050505092915050565b601f8211156106dc576000816000526020600020601f850160051c810160208610156130665750805b601f850160051c820191505b8181101561308557828155600101613072565b505050505050565b81516001600160401b038111156130a6576130a6612855565b6130ba816130b48454612e06565b8461303d565b602080601f8311600181146130ef57600084156130d75750858301515b600019600386901b1c1916600185901b178555613085565b600085815260208120601f198616915b8281101561311e578886015182559484019460019091019084016130ff565b508582101561313c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561050157610501612e3a565b6c1131b430b63632b733b2911d1160991b8152815160009061318881600d850160208701612ae5565b601160f91b600d939091019283015250600e01919050565b6000602082840312156131b257600080fd5b5051919050565b600083516131cb818460208801612ae5565b9190910191825250602001919050565b8181038181111561050157610501612e3a56feffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63255197e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f00a26469706673582212206267bdd6ae13c948b83275fcdafdc1366dd287cbf41ef689e1d49904a10ea33e64736f6c6343000817003397e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f00"; + + private static String librariesLinkedBinary; + + public static final String FUNC_REPLAYABLE_NONCE_KEY = "REPLAYABLE_NONCE_KEY"; + + public static final String FUNC_ADDOWNERADDRESS = "addOwnerAddress"; + + public static final String FUNC_ADDOWNERPUBLICKEY = "addOwnerPublicKey"; + + public static final String FUNC_CANSKIPCHAINIDVALIDATION = "canSkipChainIdValidation"; + + public static final String FUNC_DOMAINSEPARATOR = "domainSeparator"; + + public static final String FUNC_EIP712DOMAIN = "eip712Domain"; + + public static final String FUNC_ENTRYPOINT = "entryPoint"; + + public static final String FUNC_EXECUTE = "execute"; + + public static final String FUNC_EXECUTEBATCH = "executeBatch"; + + public static final String FUNC_EXECUTEWITHOUTCHAINIDVALIDATION = "executeWithoutChainIdValidation"; + + public static final String FUNC_GETUSEROPHASHWITHOUTCHAINID = "getUserOpHashWithoutChainId"; + + public static final String FUNC_IMPLEMENTATION = "implementation"; + + public static final String FUNC_INITIALIZE = "initialize"; + + public static final String FUNC_ISOWNERADDRESS = "isOwnerAddress"; + + public static final String FUNC_ISOWNERBYTES = "isOwnerBytes"; + + public static final String FUNC_ISOWNERPUBLICKEY = "isOwnerPublicKey"; + + public static final String FUNC_ISVALIDSIGNATURE = "isValidSignature"; + + public static final String FUNC_NEXTOWNERINDEX = "nextOwnerIndex"; + + public static final String FUNC_OWNERATINDEX = "ownerAtIndex"; + + public static final String FUNC_PROXIABLEUUID = "proxiableUUID"; + + public static final String FUNC_REMOVEOWNERATINDEX = "removeOwnerAtIndex"; + + public static final String FUNC_REPLAYSAFEHASH = "replaySafeHash"; + + public static final String FUNC_UPGRADETOANDCALL = "upgradeToAndCall"; + + public static final String FUNC_VALIDATEUSEROP = "validateUserOp"; + + public static final Event ADDOWNER_EVENT = new Event("AddOwner", + Arrays.>asList(new TypeReference(true) { + }, new TypeReference() { + })); + ; + + public static final Event REMOVEOWNER_EVENT = new Event("RemoveOwner", + Arrays.>asList(new TypeReference(true) { + }, new TypeReference() { + })); + ; + + public static final Event UPGRADED_EVENT = new Event("Upgraded", + Arrays.>asList(new TypeReference

(true) { + })); + ; + + @Deprecated + protected CoinbaseSmartWallet(String contractAddress, Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected CoinbaseSmartWallet(String contractAddress, Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected CoinbaseSmartWallet(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected CoinbaseSmartWallet(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public RemoteFunctionCall REPLAYABLE_NONCE_KEY() { + final Function function = new Function(FUNC_REPLAYABLE_NONCE_KEY, + Arrays.asList(), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall addOwnerAddress(String owner) { + final Function function = new Function( + FUNC_ADDOWNERADDRESS, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall addOwnerPublicKey(byte[] x, byte[] y) { + final Function function = new Function( + FUNC_ADDOWNERPUBLICKEY, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(x), + new org.web3j.abi.datatypes.generated.Bytes32(y)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall canSkipChainIdValidation(byte[] functionSelector) { + final Function function = new Function(FUNC_CANSKIPCHAINIDVALIDATION, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes4(functionSelector)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall domainSeparator() { + final Function function = new Function(FUNC_DOMAINSEPARATOR, + Arrays.asList(), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall>> eip712Domain( + ) { + final Function function = new Function(FUNC_EIP712DOMAIN, + Arrays.asList(), + Arrays.>asList(new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + }, new TypeReference
() { + }, new TypeReference() { + }, new TypeReference>() { + })); + return new RemoteFunctionCall>>(function, + new Callable>>() { + @Override + public Tuple7> call( + ) throws Exception { + List results = executeCallMultipleValueReturn(function); + return new Tuple7>( + (byte[]) results.get(0).getValue(), + (String) results.get(1).getValue(), + (String) results.get(2).getValue(), + (BigInteger) results.get(3).getValue(), + (String) results.get(4).getValue(), + (byte[]) results.get(5).getValue(), + convertToNative((List) results.get(6).getValue())); + } + }); + } + + public RemoteFunctionCall entryPoint() { + final Function function = new Function(FUNC_ENTRYPOINT, + Arrays.asList(), + Arrays.>asList(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall execute(String target, BigInteger value, + byte[] data, BigInteger weiValue) { + final Function function = new Function( + FUNC_EXECUTE, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, target), + new org.web3j.abi.datatypes.generated.Uint256(value), + new org.web3j.abi.datatypes.DynamicBytes(data)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall executeBatch(List calls, + BigInteger weiValue) { + final Function function = new Function( + FUNC_EXECUTEBATCH, + Arrays.asList(new org.web3j.abi.datatypes.DynamicArray(Call.class, calls)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall executeWithoutChainIdValidation(byte[] data, + BigInteger weiValue) { + final Function function = new Function( + FUNC_EXECUTEWITHOUTCHAINIDVALIDATION, + Arrays.asList(new org.web3j.abi.datatypes.DynamicBytes(data)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall getUserOpHashWithoutChainId(UserOperation userOp) { + final Function function = new Function(FUNC_GETUSEROPHASHWITHOUTCHAINID, + Arrays.asList(userOp), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall implementation() { + final Function function = new Function(FUNC_IMPLEMENTATION, + Arrays.asList(), + Arrays.>asList(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall initialize(List owners, + BigInteger weiValue) { + final Function function = new Function( + FUNC_INITIALIZE, + Arrays.asList(new org.web3j.abi.datatypes.DynamicArray( + org.web3j.abi.datatypes.DynamicBytes.class, + org.web3j.abi.Utils.typeMap(owners, org.web3j.abi.datatypes.DynamicBytes.class))), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall isOwnerAddress(String account) { + final Function function = new Function(FUNC_ISOWNERADDRESS, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, account)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall isOwnerBytes(byte[] account) { + final Function function = new Function(FUNC_ISOWNERBYTES, + Arrays.asList(new org.web3j.abi.datatypes.DynamicBytes(account)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall isOwnerPublicKey(byte[] x, byte[] y) { + final Function function = new Function(FUNC_ISOWNERPUBLICKEY, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(x), + new org.web3j.abi.datatypes.generated.Bytes32(y)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall isValidSignature(byte[] hash, byte[] signature) { + final Function function = new Function(FUNC_ISVALIDSIGNATURE, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(hash), + new org.web3j.abi.datatypes.DynamicBytes(signature)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall nextOwnerIndex() { + final Function function = new Function(FUNC_NEXTOWNERINDEX, + Arrays.asList(), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall ownerAtIndex(BigInteger index) { + final Function function = new Function(FUNC_OWNERATINDEX, + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(index)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall proxiableUUID() { + final Function function = new Function(FUNC_PROXIABLEUUID, + Arrays.asList(), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall removeOwnerAtIndex(BigInteger index) { + final Function function = new Function( + FUNC_REMOVEOWNERATINDEX, + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(index)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall replaySafeHash(byte[] hash) { + final Function function = new Function(FUNC_REPLAYSAFEHASH, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(hash)), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall upgradeToAndCall(String newImplementation, + byte[] data, BigInteger weiValue) { + final Function function = new Function( + FUNC_UPGRADETOANDCALL, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, newImplementation), + new org.web3j.abi.datatypes.DynamicBytes(data)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall validateUserOp(UserOperation userOp, + byte[] userOpHash, BigInteger missingAccountFunds, BigInteger weiValue) { + final Function function = new Function( + FUNC_VALIDATEUSEROP, + Arrays.asList(userOp, + new org.web3j.abi.datatypes.generated.Bytes32(userOpHash), + new org.web3j.abi.datatypes.generated.Uint256(missingAccountFunds)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public static List getAddOwnerEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ADDOWNER_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + AddOwnerEventResponse typedResponse = new AddOwnerEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.index = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.owner = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AddOwnerEventResponse getAddOwnerEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ADDOWNER_EVENT, log); + AddOwnerEventResponse typedResponse = new AddOwnerEventResponse(); + typedResponse.log = log; + typedResponse.index = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.owner = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable addOwnerEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAddOwnerEventFromLog(log)); + } + + public Flowable addOwnerEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(ADDOWNER_EVENT)); + return addOwnerEventFlowable(filter); + } + + public static List getRemoveOwnerEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REMOVEOWNER_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + RemoveOwnerEventResponse typedResponse = new RemoveOwnerEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.index = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.owner = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static RemoveOwnerEventResponse getRemoveOwnerEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REMOVEOWNER_EVENT, log); + RemoveOwnerEventResponse typedResponse = new RemoveOwnerEventResponse(); + typedResponse.log = log; + typedResponse.index = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.owner = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable removeOwnerEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRemoveOwnerEventFromLog(log)); + } + + public Flowable removeOwnerEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(REMOVEOWNER_EVENT)); + return removeOwnerEventFlowable(filter); + } + + public static List getUpgradedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UPGRADED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + UpgradedEventResponse typedResponse = new UpgradedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static UpgradedEventResponse getUpgradedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UPGRADED_EVENT, log); + UpgradedEventResponse typedResponse = new UpgradedEventResponse(); + typedResponse.log = log; + typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable upgradedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUpgradedEventFromLog(log)); + } + + public Flowable upgradedEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(UPGRADED_EVENT)); + return upgradedEventFlowable(filter); + } + + @Deprecated + public static CoinbaseSmartWallet load(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new CoinbaseSmartWallet(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static CoinbaseSmartWallet load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new CoinbaseSmartWallet(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static CoinbaseSmartWallet load(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { + return new CoinbaseSmartWallet(contractAddress, web3j, credentials, contractGasProvider); + } + + public static CoinbaseSmartWallet load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new CoinbaseSmartWallet(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy(Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider) { + return deployRemoteCall(CoinbaseSmartWallet.class, web3j, credentials, contractGasProvider, getDeploymentBinary(), ""); + } + + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return deployRemoteCall(CoinbaseSmartWallet.class, web3j, transactionManager, contractGasProvider, getDeploymentBinary(), ""); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit) { + return deployRemoteCall(CoinbaseSmartWallet.class, web3j, credentials, gasPrice, gasLimit, getDeploymentBinary(), ""); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return deployRemoteCall(CoinbaseSmartWallet.class, web3j, transactionManager, gasPrice, gasLimit, getDeploymentBinary(), ""); + } + + private static String getDeploymentBinary() { + if (librariesLinkedBinary != null) { + return librariesLinkedBinary; + } else { + return BINARY; + } + } + + public static class Call extends DynamicStruct { + public String target; + + public BigInteger value; + + public byte[] data; + + public Call(String target, BigInteger value, byte[] data) { + super(new org.web3j.abi.datatypes.Address(160, target), + new org.web3j.abi.datatypes.generated.Uint256(value), + new org.web3j.abi.datatypes.DynamicBytes(data)); + this.target = target; + this.value = value; + this.data = data; + } + + public Call(Address target, Uint256 value, DynamicBytes data) { + super(target, value, data); + this.target = target.getValue(); + this.value = value.getValue(); + this.data = data.getValue(); + } + } + + public static class UserOperation extends DynamicStruct { + public String sender; + + public BigInteger nonce; + + public byte[] initCode; + + public byte[] callData; + + public BigInteger callGasLimit; + + public BigInteger verificationGasLimit; + + public BigInteger preVerificationGas; + + public BigInteger maxFeePerGas; + + public BigInteger maxPriorityFeePerGas; + + public byte[] paymasterAndData; + + public byte[] signature; + + public UserOperation(String sender, BigInteger nonce, byte[] initCode, byte[] callData, + BigInteger callGasLimit, BigInteger verificationGasLimit, + BigInteger preVerificationGas, BigInteger maxFeePerGas, + BigInteger maxPriorityFeePerGas, byte[] paymasterAndData, byte[] signature) { + super(new org.web3j.abi.datatypes.Address(160, sender), + new org.web3j.abi.datatypes.generated.Uint256(nonce), + new org.web3j.abi.datatypes.DynamicBytes(initCode), + new org.web3j.abi.datatypes.DynamicBytes(callData), + new org.web3j.abi.datatypes.generated.Uint256(callGasLimit), + new org.web3j.abi.datatypes.generated.Uint256(verificationGasLimit), + new org.web3j.abi.datatypes.generated.Uint256(preVerificationGas), + new org.web3j.abi.datatypes.generated.Uint256(maxFeePerGas), + new org.web3j.abi.datatypes.generated.Uint256(maxPriorityFeePerGas), + new org.web3j.abi.datatypes.DynamicBytes(paymasterAndData), + new org.web3j.abi.datatypes.DynamicBytes(signature)); + this.sender = sender; + this.nonce = nonce; + this.initCode = initCode; + this.callData = callData; + this.callGasLimit = callGasLimit; + this.verificationGasLimit = verificationGasLimit; + this.preVerificationGas = preVerificationGas; + this.maxFeePerGas = maxFeePerGas; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.paymasterAndData = paymasterAndData; + this.signature = signature; + } + + public UserOperation(Address sender, Uint256 nonce, DynamicBytes initCode, + DynamicBytes callData, Uint256 callGasLimit, Uint256 verificationGasLimit, + Uint256 preVerificationGas, Uint256 maxFeePerGas, Uint256 maxPriorityFeePerGas, + DynamicBytes paymasterAndData, DynamicBytes signature) { + super(sender, nonce, initCode, callData, callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas, paymasterAndData, signature); + this.sender = sender.getValue(); + this.nonce = nonce.getValue(); + this.initCode = initCode.getValue(); + this.callData = callData.getValue(); + this.callGasLimit = callGasLimit.getValue(); + this.verificationGasLimit = verificationGasLimit.getValue(); + this.preVerificationGas = preVerificationGas.getValue(); + this.maxFeePerGas = maxFeePerGas.getValue(); + this.maxPriorityFeePerGas = maxPriorityFeePerGas.getValue(); + this.paymasterAndData = paymasterAndData.getValue(); + this.signature = signature.getValue(); + } + } + + public static class AddOwnerEventResponse extends BaseEventResponse { + public BigInteger index; + + public byte[] owner; + } + + public static class RemoveOwnerEventResponse extends BaseEventResponse { + public BigInteger index; + + public byte[] owner; + } + + public static class UpgradedEventResponse extends BaseEventResponse { + public String implementation; + } +} diff --git a/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWallet.json b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWallet.json new file mode 100644 index 00000000..fc051eea --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWallet.json @@ -0,0 +1,719 @@ +[ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "fallback", + "stateMutability": "payable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "REPLAYABLE_NONCE_KEY", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addOwnerAddress", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addOwnerPublicKey", + "inputs": [ + { + "name": "x", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "y", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "canSkipChainIdValidation", + "inputs": [ + { + "name": "functionSelector", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "domainSeparator", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "entryPoint", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "execute", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeBatch", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct CoinbaseSmartWallet.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeWithoutChainIdValidation", + "inputs": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "getUserOpHashWithoutChainId", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct UserOperation", + "components": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "initCode", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callGasLimit", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verificationGasLimit", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "preVerificationGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxPriorityFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "paymasterAndData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "implementation", + "inputs": [], + "outputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "owners", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "isOwnerAddress", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isOwnerBytes", + "inputs": [ + { + "name": "account", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isOwnerPublicKey", + "inputs": [ + { + "name": "x", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "y", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isValidSignature", + "inputs": [ + { + "name": "hash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "result", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextOwnerIndex", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ownerAtIndex", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeOwnerAtIndex", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "replaySafeHash", + "inputs": [ + { + "name": "hash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "validateUserOp", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct UserOperation", + "components": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "initCode", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callGasLimit", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verificationGasLimit", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "preVerificationGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxPriorityFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "paymasterAndData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "missingAccountFunds", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "validationData", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "AddOwner", + "inputs": [ + { + "name": "index", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "owner", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RemoveOwner", + "inputs": [ + { + "name": "index", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "owner", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyOwner", + "inputs": [ + { + "name": "owner", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "Initialized", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidEthereumAddressOwner", + "inputs": [ + { + "name": "owner", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidNonceKey", + "inputs": [ + { + "name": "key", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidOwnerBytesLength", + "inputs": [ + { + "name": "owner", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "NoOwnerAtIndex", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "SelectorNotAllowed", + "inputs": [ + { + "name": "selector", + "type": "bytes4", + "internalType": "bytes4" + } + ] + }, + { + "type": "error", + "name": "Unauthorized", + "inputs": [] + }, + { + "type": "error", + "name": "UnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UpgradeFailed", + "inputs": [] + } +] \ No newline at end of file diff --git a/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletBinary.txt b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletBinary.txt new file mode 100644 index 00000000..aa416366 --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletBinary.txt @@ -0,0 +1 @@ +0x60a0604052306080523480156200001557600080fd5b50604080516001808252818301909252600091816020015b60608152602001906001900390816200002d57905050604080516000602082015291925001604051602081830303815290604052816000815181106200007757620000776200037a565b60209081029190910101526200008d8162000094565b50620005b0565b60005b81518110156200022657818181518110620000b657620000b66200037a565b602002602001015151602014158015620000ee5750818181518110620000e057620000e06200037a565b602002602001015151604014155b1562000136578181815181106200010957620001096200037a565b60200260200101516040516327755b9160e11b81526004016200012d9190620003b6565b60405180910390fd5b8181815181106200014b576200014b6200037a565b60200260200101515160201480156200019357506001600160a01b0380168282815181106200017e576200017e6200037a565b60200260200101516200019190620003eb565b115b15620001d257818181518110620001ae57620001ae6200037a565b602002602001015160405163bff1ac6560e01b81526004016200012d9190620003b6565b6200021d828281518110620001eb57620001eb6200037a565b6020026020010151620002036200022a60201b60201c565b8054906000620002138362000413565b909155506200023d565b60010162000097565b5050565b6000805160206200383783398151915290565b620002488262000326565b156200026b578160405163468b12ad60e11b81526004016200012d9190620003b6565b600160008051602062003837833981519152600201836040516200029091906200043b565b908152604051908190036020019020805491151560ff1990921691909117905581620002c86000805160206200383783398151915290565b60008381526001919091016020526040902090620002e79082620004e4565b50807f38109edc26e166b5579352ce56a50813177eb25208fd90d61f2f378386220220836040516200031a9190620003b6565b60405180910390a25050565b600060008051602062003837833981519152600201826040516200034b91906200043b565b9081526040519081900360200190205460ff1692915050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b60005b83811015620003ad57818101518382015260200162000393565b50506000910152565b6020815260008251806020840152620003d781604085016020870162000390565b601f01601f19169190910160400192915050565b805160208083015191908110156200040d576000198160200360031b1b821691505b50919050565b6000600182016200043457634e487b7160e01b600052601160045260246000fd5b5060010190565b600082516200044f81846020870162000390565b9190910192915050565b600181811c908216806200046e57607f821691505b6020821081036200040d57634e487b7160e01b600052602260045260246000fd5b601f821115620004df576000816000526020600020601f850160051c81016020861015620004ba5750805b601f850160051c820191505b81811015620004db57828155600101620004c6565b5050505b505050565b81516001600160401b0381111562000500576200050062000364565b620005188162000511845462000459565b846200048f565b602080601f831160018114620005505760008415620005375750858301515b600019600386901b1c1916600185901b178555620004db565b600085815260208120601f198616915b82811015620005815788860151825594840194600190910190840162000560565b5085821015620005a05787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b608051613264620005d36000396000818161081e015261095501526132646000f3fe60806040526004361061014f5760003560e01c806372de3b5a116100b6578063b0d691fe1161006f578063b0d691fe146103f4578063b61d27f61461041b578063bf6ba1fc1461042e578063ce1506be14610441578063d948fd2e14610461578063f698da251461048357610156565b806372de3b5a1461032957806384b0196e1461034957806388ce4c7c146103715780638ea69029146103875780639f9bcb34146103b4578063a2e1a8d8146103d457610156565b80633a871cdd116101085780633a871cdd146102655780634f1ef286146102865780634f6e7f221461029957806352d1902d146102b95780635c60da1b146102ce5780636f2de70e1461031657610156565b8063066a1eb7146101845780630f0f3f24146101b95780631626ba7e146101d95780631ca5393f1461021257806329565e3b1461023257806334fcd5be1461025257610156565b3661015657005b60003560e01c63bc197c81811463f23a6e6182141763150b7a028214171561018257806020526020603cf35b005b34801561019057600080fd5b506101a461019f366004612769565b610498565b60405190151581526020015b60405180910390f35b3480156101c557600080fd5b506101826101d43660046127a7565b610507565b3480156101e557600080fd5b506101f96101f436600461280a565b61053f565b6040516001600160e01b031990911681526020016101b0565b34801561021e57600080fd5b506101a461022d366004612940565b610579565b34801561023e57600080fd5b5061018261024d366004612769565b6105b4565b6101826102603660046129b8565b6105dd565b610278610273366004612a12565b6106e1565b6040519081526020016101b0565b610182610294366004612a5f565b61081c565b3480156102a557600080fd5b506102786102b4366004612a98565b610900565b3480156102c557600080fd5b50610278610951565b3480156102da57600080fd5b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545b6040516001600160a01b0390911681526020016101b0565b6101826103243660046129b8565b6109b1565b34801561033557600080fd5b50610182610344366004612acc565b6109f1565b34801561035557600080fd5b5061035e610ade565b6040516101b09796959493929190612b35565b34801561037d57600080fd5b5061027861210581565b34801561039357600080fd5b506103a76103a2366004612acc565b610b05565b6040516101b09190612bce565b3480156103c057600080fd5b506101a46103cf366004612be1565b610bc6565b3480156103e057600080fd5b506101a46103ef3660046127a7565b610c42565b34801561040057600080fd5b50735ff137d4b0fdcd49dca30c7cf57e578a026d27896102fe565b610182610429366004612c0b565b610c88565b61018261043c366004612c64565b610cec565b34801561044d57600080fd5b5061027861045c366004612acc565b610dad565b34801561046d57600080fd5b5060008051602061320f83398151915254610278565b34801561048f57600080fd5b50610278610db8565b60408051602081018490529081018290526000907f97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f029060600160408051601f19818403018152908290526104eb91612c99565b9081526040519081900360200190205460ff1690505b92915050565b61050f610e3e565b604080516001600160a01b038316602082015261053c91015b604051602081830303815290604052610e70565b50565b600061055461054d85610dad565b8484610e9b565b156105675750630b135d3f60e11b610572565b506001600160e01b03195b9392505050565b600060008051602061320f8339815191526002018260405161059b9190612c99565b9081526040519081900360200190205460ff1692915050565b6105bc610e3e565b60408051602081018490529081018290526105d990606001610528565b5050565b33735ff137d4b0fdcd49dca30c7cf57e578a026d27891461060057610600610e3e565b60005b818110156106dc576106d483838381811061062057610620612cb5565b90506020028101906106329190612ccb565b6106409060208101906127a7565b84848481811061065257610652612cb5565b90506020028101906106649190612ccb565b6020013585858581811061067a5761067a612cb5565b905060200281019061068c9190612ccb565b61069a906040810190612ce1565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fb092505050565b600101610603565b505050565b600033735ff137d4b0fdcd49dca30c7cf57e578a026d278914610716576040516282b42960e81b815260040160405180910390fd5b81602085013560401c600461072e6060880188612ce1565b90501015801561077257506107466060870187612ce1565b61075591600491600091612d27565b61075e91612d51565b6001600160e01b03191663bf6ba1fc60e01b145b156107b15761078086610900565b945061210581146107ac57604051632ef3781360e01b8152600481018290526024015b60405180910390fd5b6107d6565b61210581036107d657604051632ef3781360e01b8152600481018290526024016107a3565b6107ed856107e8610140890189612ce1565b610e9b565b156107fc576000925050610802565b60019250505b80156108145760003860003884335af1505b509392505050565b7f000000000000000000000000000000000000000000000000000000000000000030810361085257639f03a0266000526004601cfd5b61085b84611020565b8360601b60601c93506352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80602060016004601d895afa51146108ad576355299b496001526004601dfd5b847fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600038a284905581156108fa57604051828482376000388483885af46108f8573d6000823e3d81fd5b505b50505050565b600061090b82611028565b604080516020810192909252735ff137d4b0fdcd49dca30c7cf57e578a026d2789908201526060015b604051602081830303815290604052805190602001209050919050565b60007f000000000000000000000000000000000000000000000000000000000000000030811461098957639f03a0266000526004601cfd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc91505b5090565b60008051602061320f83398151915254156109df576040516302ed543d60e51b815260040160405180910390fd5b6105d96109ec8284612d81565b611041565b6109f9610e3e565b6000610a0482610b05565b90508051600003610a2b5760405163340c473d60e11b8152600481018390526024016107a3565b6040517f97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f0290610a5b908390612c99565b908152604051908190036020019020805460ff19169055610a8760008051602061320f83398151915290565b600083815260019190910160205260408120610aa29161271f565b817fcf95bbfe6f870f8cc40482dc3dccdafd268f0e9ce0a4f24ea1bea9be64e505ff82604051610ad29190612bce565b60405180910390a25050565b600f60f81b6060806000808083610af3611193565b97989097965046955030945091925090565b60008181527f97e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f0160205260409020805460609190610b4190612e06565b80601f0160208091040260200160405190810160405280929190818152602001828054610b6d90612e06565b8015610bba5780601f10610b8f57610100808354040283529160200191610bba565b820191906000526020600020905b815481529060010190602001808311610b9d57829003601f168201915b50505050509050919050565b60006001600160e01b031982166329565e3b60e01b1480610bf757506001600160e01b031982166303c3cfc960e21b145b80610c1257506001600160e01b0319821663396f1dad60e11b145b80610c2d57506001600160e01b0319821663278f794360e11b145b15610c3a57506001919050565b506000919050565b600060008051602061320f833981519152604080516001600160a01b0385166020820152600292909201910160408051601f198184030181529082905261059b91612c99565b33735ff137d4b0fdcd49dca30c7cf57e578a026d278914610cab57610cab610e3e565b6108fa848484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fb092505050565b33735ff137d4b0fdcd49dca30c7cf57e578a026d278914610d1f576040516282b42960e81b815260040160405180910390fd5b6000610d2e6004828486612d27565b610d3791612d51565b9050610d4281610bc6565b610d6b57604051631d8370a360e11b81526001600160e01b0319821660048201526024016107a3565b6106dc30600085858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fb092505050565b6000610501826111da565b6000806000610dc5611193565b8151602080840191909120825182840120604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f9481019490945283019190915260608201524660808201523060a0820152919350915060c001604051602081830303815290604052805190602001209250505090565b610e4733610c42565b80610e5157503330145b15610e5857565b6040516282b42960e81b815260040160405180910390fd5b61053c8160008051602061320f8339815191525b8054906000610e9283612e50565b91905055611210565b600080610eaa83850185612e69565b90506000610ebb8260000151610b05565b90508051602003610f1a576001600160a01b03610ed782612ef5565b1115610ef8578060405163bff1ac6560e01b81526004016107a39190612bce565b600060208201519050610f10818885602001516112df565b9350505050610572565b8051604003610f955760008082806020019051810190610f3a9190612f19565b9150915060008460200151806020019051810190610f589190612f82565b9050610f8989604051602001610f7091815260200190565b60405160208183030381529060405260008386866113e4565b95505050505050610572565b806040516327755b9160e11b81526004016107a39190612bce565b600080846001600160a01b03168484604051610fcc9190612c99565b60006040518083038185875af1925050503d8060008114611009576040519150601f19603f3d011682016040523d82523d6000602084013e61100e565b606091505b5091509150816108f857805160208201fd5b61053c610e3e565b600061103382611754565b805190602001209050919050565b60005b81518110156105d95781818151811061105f5761105f612cb5565b602002602001015151602014158015611093575081818151811061108557611085612cb5565b602002602001015151604014155b156110cc578181815181106110aa576110aa612cb5565b60200260200101516040516327755b9160e11b81526004016107a39190612bce565b8181815181106110de576110de612cb5565b602002602001015151602014801561112057506001600160a01b03801682828151811061110d5761110d612cb5565b602002602001015161111e90612ef5565b115b156111595781818151811061113757611137612cb5565b602002602001015160405163bff1ac6560e01b81526004016107a39190612bce565b61118b82828151811061116e5761116e612cb5565b6020026020010151610e8460008051602061320f83398151915290565b600101611044565b604080518082018252601581527410dbda5b98985cd94814db585c9d0815d85b1b195d605a1b602080830191909152825180840190935260018352603160f81b9083015291565b60006111e4610db8565b6111ed83611827565b60405161190160f01b602082015260228101929092526042820152606201610934565b61121982610579565b15611239578160405163468b12ad60e11b81526004016107a39190612bce565b600160008051602061320f8339815191526002018360405161125b9190612c99565b908152604051908190036020019020805491151560ff199092169190911790558161129160008051602061320f83398151915290565b600083815260019190910160205260409020906112ae908261308d565b50807f38109edc26e166b5579352ce56a50813177eb25208fd90d61f2f37838622022083604051610ad29190612bce565b6001600160a01b03909216916000831561057257604051836000526020830151604052604083510361134f576040830151601b8160ff1c016020528060011b60011c60605250602060016080600060015afa805186183d151761134d57506000606052604052506001610572565b505b604183510361139557606083015160001a6020526040830151606052602060016080600060015afa805186183d151761139357506000606052604052506001610572565b505b600060605280604052631626ba7e60e01b808252846004830152602482016040815284516020018060448501828860045afa505060208160443d01858a5afa9051909114169150509392505050565b60007f7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a88460a00151111561141a5750600061174b565b606084015160009061143d9061143181601561314c565b60208801519190611862565b90507fff1a2a9176d650e4a99dedb58f1793003935130579fe17b5a3f698ac5b00e63481805190602001201461147757600091505061174b565b6000611485886001806118c8565b604051602001611495919061315f565b604051602081830303815290604052905060006114cd8760400151835189604001516114c1919061314c565b60208a01519190611862565b905081805190602001208180519060200120146114f0576000935050505061174b565b86518051600160f81b918291602090811061150d5761150d612cb5565b0160200151166001600160f81b0319161461152e576000935050505061174b565b878015611566575086518051600160fa1b918291602090811061155357611553612cb5565b0160200151166001600160f81b03191614155b15611577576000935050505061174b565b60006002886020015160405161158d9190612c99565b602060405180830381855afa1580156115aa573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906115cd91906131a0565b9050600060028960000151836040516020016115ea9291906131b9565b60408051601f198184030181529082905261160491612c99565b602060405180830381855afa158015611621573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061164491906131a0565b6080808b015160a0808d015160408051602081018790529081019390935260608301529181018b905290810189905290915060009060c00160405160208183030381529060405290506000806101006001600160a01b0316836040516116aa9190612c99565b600060405180830381855afa9150503d80600081146116e5576040519150601f19603f3d011682016040523d82523d6000602084013e6116ea565b606091505b508051919350915015158280156116fe5750805b1561172a578180602001905181019061171791906131a0565b600114995050505050505050505061174b565b61173f858e608001518f60a001518f8f6119bd565b99505050505050505050505b95945050505050565b606081356020830135600061177461176f6040870187612ce1565b611aa0565b9050600061178861176f6060880188612ce1565b9050608086013560a087013560c088013560e08901356101008a013560006117b761176f6101208e018e612ce1565b604080516001600160a01b039c909c1660208d01528b81019a909a5260608b019890985250608089019590955260a088019390935260c087019190915260e08601526101008501526101208401526101408084019190915281518084039091018152610160909201905292915050565b604080517f9b493d222105fee7df163ab5d57f0bf1ffd2da04dd5fafbe10b54c41c1adc6576020820152908101829052600090606001610934565b60608351828111611871578092505b83811161187c578093505b5081831015610572575060405182820380825293830193601f19601f820181165b868101518482015281018061189d5750600083830160200152603f9091011681016040529392505050565b606083518015610814576003600282010460021b60405192507f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f526106708515027f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f18603f52602083018181018388602001018051600082525b60038a0199508951603f8160121c1651600053603f81600c1c1651600153603f8160061c1651600253603f811651600353506000518452600484019350828410611944579052602001604052613d3d60f01b60038406600204808303919091526000861515909102918290035290038252509392505050565b60008415806119da57506000805160206131ef8339815191528510155b806119e3575083155b806119fc57506000805160206131ef8339815191528410155b15611a095750600061174b565b611a138383611ab3565b611a1f5750600061174b565b6000611a2a85611bad565b905060006000805160206131ef833981519152828909905060006000805160206131ef83398151915283890990506000611a6687878585611c1f565b90506000805160206131ef833981519152611a8f8a6000805160206131ef8339815191526131db565b8208159a9950505050505050505050565b6000604051828085833790209392505050565b600082158015611ac1575081155b80611ad95750600160601b63ffffffff60c01b031983145b80611af15750600160601b63ffffffff60c01b031982145b15611afe57506000610501565b6000600160601b63ffffffff60c01b031983840990506000600160601b63ffffffff60c01b0319807fffffffff00000001000000000000000000000000fffffffffffffffffffffffc8709600160601b63ffffffff60c01b031987600160601b63ffffffff60c01b0319898a0909089050600160601b63ffffffff60c01b03197f5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b820891909114949350505050565b600060405160208152602080820152602060408201528260608201527fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f60808201526000805160206131ef83398151915260a082015260208160c0836005600019fa611c1857600080fd5b5192915050565b600080808060ff818088158015611c34575087155b15611c4857600096505050505050506122e1565b611c947f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2967f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f58d8d6122e9565b909250905081158015611ca5575080155b15611cd3576000805160206131ef833981519152886000805160206131ef833981519152038a089850600097505b600189841c16600189851c1660011b015b80611d065760018403935060018a851c1660018a861c1660011b019050611ce4565b50600189841c16600189851c1660011b01955060018603611d68577f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29696507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f593505b60028603611d77578a96508993505b60038603611d86578196508093505b60018303925060019550600194505b82600019111561226a57600160601b63ffffffff60c01b031984600209600160601b63ffffffff60c01b0319818209600160601b63ffffffff60c01b0319818a09600160601b63ffffffff60c01b03198284099250600160601b63ffffffff60c01b031980600160601b63ffffffff60c01b03198b8d08600160601b63ffffffff60c01b03198c600160601b63ffffffff60c01b0319038e0809600309600160601b63ffffffff60c01b03198985099850600160601b63ffffffff60c01b03198a84099950600160601b63ffffffff60c01b031980836002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b0319838409089a50600160601b63ffffffff60c01b03198083600160601b63ffffffff60c01b0319038d0882099250600160601b63ffffffff60c01b031983600160601b63ffffffff60c01b03198a870908975060018d881c1660018d891c1660011b01905080611f125787600160601b63ffffffff60c01b03190397505050505061225f565b60018103611f61577f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29693507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f592505b60028103611f70578e93508d92505b60038103611f7f578593508492505b89611f98575091985060019750879650945061225f9050565b600160601b63ffffffff60c01b031988600160601b63ffffffff60c01b03198b860908600160601b63ffffffff60c01b03198c600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b03198d880908935080612151578361215157600160601b63ffffffff60c01b0319896002600160601b0363ffffffff60c01b0319099450600160601b63ffffffff60c01b03198586099350600160601b63ffffffff60c01b0319848d099250600160601b63ffffffff60c01b03198486099450600160601b63ffffffff60c01b0319808c600160601b63ffffffff60c01b0319038e08600160601b63ffffffff60c01b03198d8f08099050600160601b63ffffffff60c01b0319816003099150600160601b63ffffffff60c01b03198a86099950600160601b63ffffffff60c01b03198b85099a50600160601b63ffffffff60c01b031980846002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b0319848509089b50600160601b63ffffffff60c01b0319808d600160601b63ffffffff60c01b031903850883099350600160601b63ffffffff60c01b0319808a870985089850505050505061225f565b600160601b63ffffffff60c01b03198485099150600160601b63ffffffff60c01b0319848309600160601b63ffffffff60c01b0319838d099b50600160601b63ffffffff60c01b0319818c099a50600160601b63ffffffff60c01b0319838e09600160601b63ffffffff60c01b031980826002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b031984600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b031987880908089350600160601b63ffffffff60c01b031980838d09600160601b63ffffffff60c01b031985600160601b63ffffffff60c01b031988600160601b63ffffffff60c01b031903860809089a50505050809a50505050505b600183039250611d95565b60405186606082015260208152602080820152602060408201526002600160601b0363ffffffff60c01b03196080820152600160601b63ffffffff60c01b031960a082015260208160c0836005600019fa6122c457600080fd5b600160601b63ffffffff60c01b0319815189099750505050505050505b949350505050565b60008080808661230057858593509350505061236e565b8461231257878793509350505061236e565b858814801561232057508487145b15612341576123328888600180612377565b929a509098509250905061235b565b61235088886001808a8a6124d2565b929a50909850925090505b61236788888484612656565b9350935050505b94509492505050565b600080600080600160601b63ffffffff60c01b0319876002099350600160601b63ffffffff60c01b03198485099150600160601b63ffffffff60c01b03198289099050600160601b63ffffffff60c01b03198285099250600160601b63ffffffff60c01b03198683099150600160601b63ffffffff60c01b031980600160601b63ffffffff60c01b0319888b08600160601b63ffffffff60c01b031989600160601b63ffffffff60c01b0319038c08096003099550600160601b63ffffffff60c01b031980826002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b0319888909089350600160601b63ffffffff60c01b03198085600160601b63ffffffff60c01b031903830887099750600160601b63ffffffff60c01b03198584099050600160601b63ffffffff60c01b031980888509600160601b63ffffffff60c01b03190389089250945094509450949050565b600080600080886000036124f157508492508391506001905080612649565b600160601b63ffffffff60c01b0319988903988981898809089450600160601b63ffffffff60c01b03198a600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b03198a8909089550600160601b63ffffffff60c01b03198687099350600160601b63ffffffff60c01b03198685099250600160601b63ffffffff60c01b03198489099150600160601b63ffffffff60c01b03198388099050600160601b63ffffffff60c01b0319848b099750600160601b63ffffffff60c01b031980896002600160601b0363ffffffff60c01b031909600160601b63ffffffff60c01b031985600160601b63ffffffff60c01b031903600160601b63ffffffff60c01b0319898a0908089350600160601b63ffffffff60c01b031980848b09600160601b63ffffffff60c01b031987600160601b63ffffffff60c01b031988600160601b63ffffffff60c01b0319038d08090892505b9650965096509692505050565b6000806000612664846126c3565b9050600160601b63ffffffff60c01b031981870991506000600160601b63ffffffff60c01b03198287099050600160601b63ffffffff60c01b03198182099150600160601b63ffffffff60c01b03198289099350505094509492505050565b600060405160208152602080820152602060408201528260608201526002600160601b0363ffffffff60c01b03196080820152600160601b63ffffffff60c01b031960a082015260208160c0836005600019fa611c1857600080fd5b50805461272b90612e06565b6000825580601f1061273b575050565b601f01602090049060005260206000209081019061053c91905b808211156109ad5760008155600101612755565b6000806040838503121561277c57600080fd5b50508035926020909101359150565b80356001600160a01b03811681146127a257600080fd5b919050565b6000602082840312156127b957600080fd5b6105728261278b565b60008083601f8401126127d457600080fd5b5081356001600160401b038111156127eb57600080fd5b60208301915083602082850101111561280357600080fd5b9250929050565b60008060006040848603121561281f57600080fd5b8335925060208401356001600160401b0381111561283c57600080fd5b612848868287016127c2565b9497909650939450505050565b634e487b7160e01b600052604160045260246000fd5b60405160c081016001600160401b038111828210171561288d5761288d612855565b60405290565b604051601f8201601f191681016001600160401b03811182821017156128bb576128bb612855565b604052919050565b60006001600160401b038211156128dc576128dc612855565b50601f01601f191660200190565b600082601f8301126128fb57600080fd5b813561290e612909826128c3565b612893565b81815284602083860101111561292357600080fd5b816020850160208301376000918101602001919091529392505050565b60006020828403121561295257600080fd5b81356001600160401b0381111561296857600080fd5b6122e1848285016128ea565b60008083601f84011261298657600080fd5b5081356001600160401b0381111561299d57600080fd5b6020830191508360208260051b850101111561280357600080fd5b600080602083850312156129cb57600080fd5b82356001600160401b038111156129e157600080fd5b6129ed85828601612974565b90969095509350505050565b60006101608284031215612a0c57600080fd5b50919050565b600080600060608486031215612a2757600080fd5b83356001600160401b03811115612a3d57600080fd5b612a49868287016129f9565b9660208601359650604090950135949350505050565b600080600060408486031215612a7457600080fd5b612a7d8461278b565b925060208401356001600160401b0381111561283c57600080fd5b600060208284031215612aaa57600080fd5b81356001600160401b03811115612ac057600080fd5b6122e1848285016129f9565b600060208284031215612ade57600080fd5b5035919050565b60005b83811015612b00578181015183820152602001612ae8565b50506000910152565b60008151808452612b21816020860160208601612ae5565b601f01601f19169290920160200192915050565b60ff60f81b881681526000602060e06020840152612b5660e084018a612b09565b8381036040850152612b68818a612b09565b606085018990526001600160a01b038816608086015260a0850187905284810360c08601528551808252602080880193509091019060005b81811015612bbc57835183529284019291840191600101612ba0565b50909c9b505050505050505050505050565b6020815260006105726020830184612b09565b600060208284031215612bf357600080fd5b81356001600160e01b03198116811461057257600080fd5b60008060008060608587031215612c2157600080fd5b612c2a8561278b565b93506020850135925060408501356001600160401b03811115612c4c57600080fd5b612c58878288016127c2565b95989497509550505050565b60008060208385031215612c7757600080fd5b82356001600160401b03811115612c8d57600080fd5b6129ed858286016127c2565b60008251612cab818460208701612ae5565b9190910192915050565b634e487b7160e01b600052603260045260246000fd5b60008235605e19833603018112612cab57600080fd5b6000808335601e19843603018112612cf857600080fd5b8301803591506001600160401b03821115612d1257600080fd5b60200191503681900382131561280357600080fd5b60008085851115612d3757600080fd5b83861115612d4457600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015612d795780818660040360031b1b83161692505b505092915050565b60006001600160401b0380841115612d9b57612d9b612855565b8360051b6020612dad60208301612893565b86815291850191602081019036841115612dc657600080fd5b865b84811015612dfa57803586811115612de05760008081fd5b612dec36828b016128ea565b845250918301918301612dc8565b50979650505050505050565b600181811c90821680612e1a57607f821691505b602082108103612a0c57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600060018201612e6257612e62612e3a565b5060010190565b600060208284031215612e7b57600080fd5b81356001600160401b0380821115612e9257600080fd5b9083019060408286031215612ea657600080fd5b604051604081018181108382111715612ec157612ec1612855565b60405282358152602083013582811115612eda57600080fd5b612ee6878286016128ea565b60208301525095945050505050565b80516020808301519190811015612a0c5760001960209190910360031b1b16919050565b60008060408385031215612f2c57600080fd5b505080516020909101519092909150565b600082601f830112612f4e57600080fd5b8151612f5c612909826128c3565b818152846020838601011115612f7157600080fd5b6122e1826020830160208701612ae5565b600060208284031215612f9457600080fd5b81516001600160401b0380821115612fab57600080fd5b9083019060c08286031215612fbf57600080fd5b612fc761286b565b825182811115612fd657600080fd5b612fe287828601612f3d565b825250602083015182811115612ff757600080fd5b61300387828601612f3d565b60208301525060408301516040820152606083015160608201526080830151608082015260a083015160a082015280935050505092915050565b601f8211156106dc576000816000526020600020601f850160051c810160208610156130665750805b601f850160051c820191505b8181101561308557828155600101613072565b505050505050565b81516001600160401b038111156130a6576130a6612855565b6130ba816130b48454612e06565b8461303d565b602080601f8311600181146130ef57600084156130d75750858301515b600019600386901b1c1916600185901b178555613085565b600085815260208120601f198616915b8281101561311e578886015182559484019460019091019084016130ff565b508582101561313c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561050157610501612e3a565b6c1131b430b63632b733b2911d1160991b8152815160009061318881600d850160208701612ae5565b601160f91b600d939091019283015250600e01919050565b6000602082840312156131b257600080fd5b5051919050565b600083516131cb818460208801612ae5565b9190910191825250602001919050565b8181038181111561050157610501612e3a56feffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63255197e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f00a26469706673582212206267bdd6ae13c948b83275fcdafdc1366dd287cbf41ef689e1d49904a10ea33e64736f6c6343000817003397e2c6aad4ce5d562ebfaa00db6b9e0fb66ea5d8162ed5b243f51a2e03086f00 \ No newline at end of file diff --git a/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactory.java b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactory.java new file mode 100644 index 00000000..ffb496d3 --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactory.java @@ -0,0 +1,166 @@ +package org.xmtp.android.library.artifact; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 1.6.1. + */ +@SuppressWarnings("rawtypes") +public class CoinbaseSmartWalletFactory extends Contract { + public static final String BINARY = "0x60a06040526040516105eb3803806105eb83398101604081905261002291610033565b6001600160a01b0316608052610063565b60006020828403121561004557600080fd5b81516001600160a01b038116811461005c57600080fd5b9392505050565b60805161056061008b6000396000818160a60152818161013c015261023b01526105606000f3fe60806040526004361061003f5760003560e01c8063250b1b41146100445780633ffba36f146100815780635c60da1b14610094578063db4c545e146100c8575b600080fd5b34801561005057600080fd5b5061006461005f3660046103b7565b6100eb565b6040516001600160a01b0390911681526020015b60405180910390f35b61006461008f3660046103b7565b610111565b3480156100a057600080fd5b506100647f000000000000000000000000000000000000000000000000000000000000000081565b3480156100d457600080fd5b506100dd6101e6565b604051908152602001610078565b60006101096100f86101e6565b61010386868661027b565b306102b1565b949350505050565b600082810361013357604051633c776be160e01b815260040160405180910390fd5b60008061016b347f000000000000000000000000000000000000000000000000000000000000000061016689898961027b565b6102d3565b935091508290508115156000036101dd57604051633796f38760e11b81526001600160a01b03841690636f2de70e906101aa90899089906004016104f2565b600060405180830381600087803b1580156101c457600080fd5b505af11580156101d8573d6000803e3d6000fd5b505050505b50509392505050565b604080517fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f360609081527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e207683526160096020527f0000000000000000000000000000000000000000000000000000000000000000601e5268603d3d8160223d3973600a52605f60212091909252600090915290565b600083838360405160200161029293929190610506565b6040516020818303038152906040528051906020012090509392505050565b600060ff60005350603592835260601b60015260155260556000908120915290565b6000806040517fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f36060527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e207660405261600960205284601e5268603d3d8160223d3973600a52605f60212060358201523060581b815260ff8153836015820152605581209150813b61037f5783605f602188f591508161037a5763301164256000526004601cfd5b6103a5565b6001925085156103a55760003860003889865af16103a55763b12d13eb6000526004601cfd5b80604052506000606052935093915050565b6000806000604084860312156103cc57600080fd5b833567ffffffffffffffff808211156103e457600080fd5b818601915086601f8301126103f857600080fd5b81358181111561040757600080fd5b8760208260051b850101111561041c57600080fd5b6020928301989097509590910135949350505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000838385526020808601955060208560051b8301018460005b878110156104e557848303601f19018952813536889003601e1901811261049b57600080fd5b8701848101903567ffffffffffffffff8111156104b757600080fd5b8036038213156104c657600080fd5b6104d1858284610432565b9a86019a9450505090830190600101610475565b5090979650505050505050565b60208152600061010960208301848661045b565b60408152600061051a60408301858761045b565b905082602083015294935050505056fea264697066735822122098bae64e62859ac8d5ed01c4927e5fce406f632b517c86f038f06fef8355dba164736f6c63430008170033"; + + private static String librariesLinkedBinary; + + public static final String FUNC_CREATEACCOUNT = "createAccount"; + + public static final String FUNC_GETADDRESS = "getAddress"; + + public static final String FUNC_IMPLEMENTATION = "implementation"; + + public static final String FUNC_INITCODEHASH = "initCodeHash"; + + @Deprecated + protected CoinbaseSmartWalletFactory(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected CoinbaseSmartWalletFactory(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected CoinbaseSmartWalletFactory(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected CoinbaseSmartWalletFactory(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public RemoteFunctionCall createAccount(List owners, + BigInteger nonce, BigInteger weiValue) { + final Function function = new Function( + FUNC_CREATEACCOUNT, + Arrays.asList(new org.web3j.abi.datatypes.DynamicArray( + org.web3j.abi.datatypes.DynamicBytes.class, + org.web3j.abi.Utils.typeMap(owners, org.web3j.abi.datatypes.DynamicBytes.class)), + new org.web3j.abi.datatypes.generated.Uint256(nonce)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall getAddress(List owners, BigInteger nonce) { + final Function function = new Function(FUNC_GETADDRESS, + Arrays.asList(new org.web3j.abi.datatypes.DynamicArray( + org.web3j.abi.datatypes.DynamicBytes.class, + org.web3j.abi.Utils.typeMap(owners, org.web3j.abi.datatypes.DynamicBytes.class)), + new org.web3j.abi.datatypes.generated.Uint256(nonce)), + Arrays.>asList(new TypeReference

() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall implementation() { + final Function function = new Function(FUNC_IMPLEMENTATION, + Arrays.asList(), + Arrays.>asList(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall initCodeHash() { + final Function function = new Function(FUNC_INITCODEHASH, + Arrays.asList(), + Arrays.>asList(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + @Deprecated + public static CoinbaseSmartWalletFactory load(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new CoinbaseSmartWalletFactory(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static CoinbaseSmartWalletFactory load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new CoinbaseSmartWalletFactory(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static CoinbaseSmartWalletFactory load(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { + return new CoinbaseSmartWalletFactory(contractAddress, web3j, credentials, contractGasProvider); + } + + public static CoinbaseSmartWalletFactory load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new CoinbaseSmartWalletFactory(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy(Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider, + BigInteger initialWeiValue, String erc4337) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, erc4337))); + return deployRemoteCall(CoinbaseSmartWalletFactory.class, web3j, credentials, contractGasProvider, getDeploymentBinary(), encodedConstructor, initialWeiValue); + } + + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider, + BigInteger initialWeiValue, String erc4337) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, erc4337))); + return deployRemoteCall(CoinbaseSmartWalletFactory.class, web3j, transactionManager, contractGasProvider, getDeploymentBinary(), encodedConstructor, initialWeiValue); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, + BigInteger initialWeiValue, String erc4337) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, erc4337))); + return deployRemoteCall(CoinbaseSmartWalletFactory.class, web3j, credentials, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor, initialWeiValue); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, + BigInteger initialWeiValue, String erc4337) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, erc4337))); + return deployRemoteCall(CoinbaseSmartWalletFactory.class, web3j, transactionManager, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor, initialWeiValue); + } + + private static String getDeploymentBinary() { + if (librariesLinkedBinary != null) { + return librariesLinkedBinary; + } else { + return BINARY; + } + } +} diff --git a/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactory.json b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactory.json new file mode 100644 index 00000000..eca83520 --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactory.json @@ -0,0 +1,92 @@ + [ + { + "type": "constructor", + "inputs": [ + { + "name": "erc4337", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "createAccount", + "inputs": [ + { + "name": "owners", + "type": "bytes[]", + "internalType": "bytes[]" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "account", + "type": "address", + "internalType": "contract CoinbaseSmartWallet" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "getAddress", + "inputs": [ + { + "name": "owners", + "type": "bytes[]", + "internalType": "bytes[]" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "predicted", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "implementation", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initCodeHash", + "inputs": [], + "outputs": [ + { + "name": "result", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "error", + "name": "OwnerRequired", + "inputs": [] + } + ] \ No newline at end of file diff --git a/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactoryBinary.txt b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactoryBinary.txt new file mode 100644 index 00000000..fb4d22ac --- /dev/null +++ b/library/src/androidTest/java/org/xmtp/android/library/artifact/CoinbaseSmartWalletFactoryBinary.txt @@ -0,0 +1 @@ +0x60a06040526040516105eb3803806105eb83398101604081905261002291610033565b6001600160a01b0316608052610063565b60006020828403121561004557600080fd5b81516001600160a01b038116811461005c57600080fd5b9392505050565b60805161056061008b6000396000818160a60152818161013c015261023b01526105606000f3fe60806040526004361061003f5760003560e01c8063250b1b41146100445780633ffba36f146100815780635c60da1b14610094578063db4c545e146100c8575b600080fd5b34801561005057600080fd5b5061006461005f3660046103b7565b6100eb565b6040516001600160a01b0390911681526020015b60405180910390f35b61006461008f3660046103b7565b610111565b3480156100a057600080fd5b506100647f000000000000000000000000000000000000000000000000000000000000000081565b3480156100d457600080fd5b506100dd6101e6565b604051908152602001610078565b60006101096100f86101e6565b61010386868661027b565b306102b1565b949350505050565b600082810361013357604051633c776be160e01b815260040160405180910390fd5b60008061016b347f000000000000000000000000000000000000000000000000000000000000000061016689898961027b565b6102d3565b935091508290508115156000036101dd57604051633796f38760e11b81526001600160a01b03841690636f2de70e906101aa90899089906004016104f2565b600060405180830381600087803b1580156101c457600080fd5b505af11580156101d8573d6000803e3d6000fd5b505050505b50509392505050565b604080517fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f360609081527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e207683526160096020527f0000000000000000000000000000000000000000000000000000000000000000601e5268603d3d8160223d3973600a52605f60212091909252600090915290565b600083838360405160200161029293929190610506565b6040516020818303038152906040528051906020012090509392505050565b600060ff60005350603592835260601b60015260155260556000908120915290565b6000806040517fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f36060527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e207660405261600960205284601e5268603d3d8160223d3973600a52605f60212060358201523060581b815260ff8153836015820152605581209150813b61037f5783605f602188f591508161037a5763301164256000526004601cfd5b6103a5565b6001925085156103a55760003860003889865af16103a55763b12d13eb6000526004601cfd5b80604052506000606052935093915050565b6000806000604084860312156103cc57600080fd5b833567ffffffffffffffff808211156103e457600080fd5b818601915086601f8301126103f857600080fd5b81358181111561040757600080fd5b8760208260051b850101111561041c57600080fd5b6020928301989097509590910135949350505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000838385526020808601955060208560051b8301018460005b878110156104e557848303601f19018952813536889003601e1901811261049b57600080fd5b8701848101903567ffffffffffffffff8111156104b757600080fd5b8036038213156104c657600080fd5b6104d1858284610432565b9a86019a9450505090830190600101610475565b5090979650505050505050565b60208152600061010960208301848661045b565b60408152600061051a60408301858761045b565b905082602083015294935050505056fea264697066735822122098bae64e62859ac8d5ed01c4927e5fce406f632b517c86f038f06fef8355dba164736f6c63430008170033 \ No newline at end of file diff --git a/library/src/androidTest/res/xml/network_security_config.xml b/library/src/androidTest/res/xml/network_security_config.xml new file mode 100644 index 00000000..ef218b2b --- /dev/null +++ b/library/src/androidTest/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + 10.0.2.2 + 127.0.0.1 + localhost + + \ No newline at end of file diff --git a/library/src/main/java/libxmtp-version.txt b/library/src/main/java/libxmtp-version.txt index 8e2ce39b..f78cf02b 100644 --- a/library/src/main/java/libxmtp-version.txt +++ b/library/src/main/java/libxmtp-version.txt @@ -1,3 +1,3 @@ -Version: 137062e8 -Branch: main -Date: 2024-10-08 19:58:58 +0000 +Version: 6937f233 +Branch: np/fix-ffi-bindings +Date: 2024-10-16 17:50:35 +0000 diff --git a/library/src/main/java/org/xmtp/android/library/Client.kt b/library/src/main/java/org/xmtp/android/library/Client.kt index f45146a6..e59ccfec 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -289,34 +289,62 @@ class Client() { } } - // This is a V3 only feature - suspend fun createOrBuild( + private suspend fun initializeV3Client( + accountAddress: String, + clientOptions: ClientOptions, + signingKey: SigningKey? = null, + ): Client { + val inboxId = getOrCreateInboxId(clientOptions, accountAddress) + + val (libXMTPClient, dbPath) = ffiXmtpClient( + clientOptions, + signingKey, + clientOptions.appContext, + null, + accountAddress, + inboxId + ) + + libXMTPClient?.let { client -> + return Client( + accountAddress, + client, + dbPath, + client.installationId().toHex(), + client.inboxId(), + clientOptions.api.env + ) + } ?: throw XMTPException("Error creating V3 client: libXMTPClient is null") + } + + // Function to create a V3 client with a signing key + suspend fun createV3( account: SigningKey, - options: ClientOptions, + options: ClientOptions? = null, ): Client { this.hasV2Client = false - val inboxId = getOrCreateInboxId(options, account.address) - + val clientOptions = options ?: ClientOptions(enableV3 = true) + val accountAddress = + if (account.isSmartContractWallet) "eip155:${account.chainId}:${account.address.lowercase()}" else account.address.lowercase() return try { - val (libXMTPClient, dbPath) = ffiXmtpClient( - options, - account, - options.appContext, - null, - account.address, - inboxId - ) + initializeV3Client(accountAddress, clientOptions, account) + } catch (e: Exception) { + throw XMTPException("Error creating V3 client: ${e.message}", e) + } + } - libXMTPClient?.let { client -> - Client( - account.address, - client, - dbPath, - client.installationId().toHex(), - client.inboxId(), - options.api.env - ) - } ?: throw XMTPException("Error creating V3 client: libXMTPClient is null") + // Function to build a V3 client without a signing key (using only address (& chainId for SCW)) + suspend fun buildV3( + address: String, + chainId: Long? = null, + options: ClientOptions? = null, + ): Client { + this.hasV2Client = false + val clientOptions = options ?: ClientOptions(enableV3 = true) + val accountAddress = + if (chainId != null) "eip155:$chainId:${address.lowercase()}" else address.lowercase() + return try { + initializeV3Client(accountAddress, clientOptions) } catch (e: Exception) { throw XMTPException("Error creating V3 client: ${e.message}", e) } @@ -420,9 +448,21 @@ class Client() { } v3Client.signatureRequest()?.let { signatureRequest -> if (account != null) { - account.sign(signatureRequest.signatureText())?.let { - signatureRequest.addEcdsaSignature(it.rawData) + if (account.isSmartContractWallet) { + val chainId = account.chainId + ?: throw XMTPException("ChainId is required for smart contract wallets") + signatureRequest.addScwSignature( + account.signSCW(signatureRequest.signatureText()), + account.address.lowercase(), + chainId.toULong(), + account.blockNumber?.toULong() + ) + } else { + account.sign(signatureRequest.signatureText())?.let { + signatureRequest.addEcdsaSignature(it.rawData) + } } + v3Client.registerIdentity(signatureRequest) } else { throw XMTPException("No signer passed but signer was required.") @@ -567,10 +607,49 @@ class Client() { } } + @Deprecated("Find now includes DMs and Groups", replaceWith = ReplaceWith("findConversation")) fun findGroup(groupId: String): Group? { val client = v3Client ?: throw XMTPException("Error no V3 client initialized") try { - return Group(this, client.group(groupId.hexToByteArray())) + return Group(this, client.conversation(groupId.hexToByteArray())) + } catch (e: Exception) { + return null + } + } + + fun findConversation(conversationId: String): Conversation? { + val client = v3Client ?: throw XMTPException("Error no V3 client initialized") + val conversation = client.conversation(conversationId.hexToByteArray()) + return if (conversation.groupMetadata().conversationType() == "dm") { + Conversation.Dm(Dm(this, conversation)) + } else if (conversation.groupMetadata().conversationType() == "group") { + Conversation.Group(Group(this, conversation)) + } else { + null + } + } + + fun findConversationByTopic(topic: String): Conversation? { + val client = v3Client ?: throw XMTPException("Error no V3 client initialized") + val regex = """/xmtp/mls/1/g-(.*?)/proto""".toRegex() + val matchResult = regex.find(topic) + val conversationId = matchResult?.groupValues?.get(1) ?: "" + val conversation = client.conversation(conversationId.hexToByteArray()) + return if (conversation.groupMetadata().conversationType() == "dm") { + Conversation.Dm(Dm(this, conversation)) + } else if (conversation.groupMetadata().conversationType() == "group") { + Conversation.Group(Group(this, conversation)) + } else { + null + } + } + + suspend fun findDm(address: String): Dm? { + val client = v3Client ?: throw XMTPException("Error no V3 client initialized") + val inboxId = + inboxIdFromAddress(address.lowercase()) ?: throw XMTPException("No inboxId present") + try { + return Dm(this, client.dmConversation(inboxId)) } catch (e: Exception) { return null } diff --git a/library/src/main/java/org/xmtp/android/library/Contacts.kt b/library/src/main/java/org/xmtp/android/library/Contacts.kt index 05df91ce..c0d0b544 100644 --- a/library/src/main/java/org/xmtp/android/library/Contacts.kt +++ b/library/src/main/java/org/xmtp/android/library/Contacts.kt @@ -47,7 +47,7 @@ enum class EntryType { fun toFfiConsentEntityType(option: EntryType): FfiConsentEntityType { return when (option) { EntryType.ADDRESS -> FfiConsentEntityType.ADDRESS - EntryType.GROUP_ID -> FfiConsentEntityType.GROUP_ID + EntryType.GROUP_ID -> FfiConsentEntityType.CONVERSATION_ID EntryType.INBOX_ID -> FfiConsentEntityType.INBOX_ID } } @@ -55,7 +55,7 @@ enum class EntryType { fun fromFfiConsentEntityType(option: FfiConsentEntityType): EntryType { return when (option) { FfiConsentEntityType.ADDRESS -> EntryType.ADDRESS - FfiConsentEntityType.GROUP_ID -> EntryType.GROUP_ID + FfiConsentEntityType.CONVERSATION_ID -> EntryType.GROUP_ID FfiConsentEntityType.INBOX_ID -> EntryType.INBOX_ID } } @@ -301,7 +301,7 @@ class ConsentList( client.v3Client?.let { return ConsentState.fromFfiConsentState( it.getConsentState( - FfiConsentEntityType.GROUP_ID, + FfiConsentEntityType.CONVERSATION_ID, groupId ) ) diff --git a/library/src/main/java/org/xmtp/android/library/Conversation.kt b/library/src/main/java/org/xmtp/android/library/Conversation.kt index b8db34d0..e7406327 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversation.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversation.kt @@ -5,6 +5,7 @@ import com.google.protobuf.kotlin.toByteString import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.runBlocking import org.xmtp.android.library.codecs.EncodedContent +import org.xmtp.android.library.libxmtp.Member import org.xmtp.android.library.libxmtp.MessageV3 import org.xmtp.android.library.messages.DecryptedMessage import org.xmtp.android.library.messages.Envelope @@ -28,154 +29,130 @@ sealed class Conversation { data class V2(val conversationV2: ConversationV2) : Conversation() data class Group(val group: org.xmtp.android.library.Group) : Conversation() + data class Dm(val dm: org.xmtp.android.library.Dm) : Conversation() - enum class Version { V1, V2, GROUP } + enum class Version { V1, V2, GROUP, DM } - // This indicates whether this a v1 or v2 conversation. val version: Version get() { return when (this) { is V1 -> Version.V1 is V2 -> Version.V2 is Group -> Version.GROUP + is Dm -> Version.DM + } + } + + val id: String + get() { + return when (this) { + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.id + is Dm -> dm.id + } + } + + val topic: String + get() { + return when (this) { + is V1 -> conversationV1.topic.description + is V2 -> conversationV2.topic + is Group -> group.topic + is Dm -> dm.topic } } - // When the conversation was first created. val createdAt: Date get() { return when (this) { is V1 -> conversationV1.sentAt is V2 -> conversationV2.createdAt is Group -> group.createdAt + is Dm -> dm.createdAt } } - // This is the address of the peer that I am talking to. - val peerAddress: String + val name: String get() { return when (this) { - is V1 -> conversationV1.peerAddress - is V2 -> conversationV2.peerAddress - is Group -> runBlocking { group.peerInboxIds().joinToString(",") } + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.name + is Dm -> dm.name } } - val peerAddresses: List + val imageUrlSquare: String get() { return when (this) { - is V1 -> listOf(conversationV1.peerAddress) - is V2 -> listOf(conversationV2.peerAddress) - is Group -> runBlocking { group.peerInboxIds() } + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.imageUrlSquare + is Dm -> dm.imageUrlSquare } } - - // This distinctly identifies between two addresses. - // Note: this will be empty for older v1 conversations. - val conversationId: String? + val description: String get() { return when (this) { - is V1 -> null - is V2 -> conversationV2.context.conversationId - is Group -> null + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.description + is Dm -> dm.description } } - - val keyMaterial: ByteArray? + val pinnedFrameUrl: String get() { return when (this) { - is V1 -> null - is V2 -> conversationV2.keyMaterial - is Group -> null + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.pinnedFrameUrl + is Dm -> dm.pinnedFrameUrl } } - suspend fun consentState(): ConsentState { + fun isCreator(): Boolean { return when (this) { - is V1 -> conversationV1.client.contacts.consentList.state(address = peerAddress) - is V2 -> conversationV2.client.contacts.consentList.state(address = peerAddress) - is Group -> group.consentState() + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.isCreator() + is Dm -> dm.isCreator() } } - /** - * This method is to create a TopicData object - * @return [TopicData] that contains all the information about the Topic, the conversation - * context and the necessary encryption data for it. - */ - fun toTopicData(): TopicData { - val data = TopicData.newBuilder() - .setCreatedNs(createdAt.time * 1_000_000) - .setPeerAddress(peerAddress) + suspend fun members(): List { return when (this) { - is V1 -> data.build() - is V2 -> data.setInvitation( - Invitation.InvitationV1.newBuilder() - .setTopic(topic) - .setContext(conversationV2.context) - .setAes256GcmHkdfSha256( - Aes256gcmHkdfsha256.newBuilder() - .setKeyMaterial(conversationV2.keyMaterial.toByteString()), - ), - ).build() - - is Group -> throw XMTPException("Groups do not support topics") + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.members() + is Dm -> dm.members() } } - fun decode(envelope: Envelope, message: MessageV3? = null): DecodedMessage { + suspend fun updateConsentState(state: ConsentState) { return when (this) { - is V1 -> conversationV1.decode(envelope) - is V2 -> conversationV2.decodeEnvelope(envelope) - is Group -> message?.decode() ?: throw XMTPException("Groups require message be passed") - } - } - - fun decodeOrNull(envelope: Envelope): DecodedMessage? { - return try { - decode(envelope) - } catch (e: Exception) { - Log.d("CONVERSATION", "discarding message that failed to decode", e) - null - } - } - - fun prepareMessage(content: T, options: SendOptions? = null): PreparedMessage { - return when (this) { - is V1 -> { - conversationV1.prepareMessage(content = content, options = options) - } - - is V2 -> { - conversationV2.prepareMessage(content = content, options = options) - } - - is Group -> throw XMTPException("Groups do not support prepared messages") // We return a encoded content not a preparedmessage which requires a envelope + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.updateConsentState(state) + is Dm -> dm.updateConsentState(state) } } - fun prepareMessage( - encodedContent: EncodedContent, - options: SendOptions? = null, - ): PreparedMessage { + suspend fun consentState(): ConsentState { return when (this) { - is V1 -> { - conversationV1.prepareMessage(encodedContent = encodedContent, options = options) - } - - is V2 -> { - conversationV2.prepareMessage(encodedContent = encodedContent, options = options) - } - - is Group -> throw XMTPException("Groups do not support prepared messages") // We return a encoded content not a preparedmessage which requires a envelope + is V1 -> conversationV1.client.contacts.consentList.state(address = peerAddress) + is V2 -> conversationV2.client.contacts.consentList.state(address = peerAddress) + is Group -> group.consentState() + is Dm -> dm.consentState() } } - suspend fun send(prepared: PreparedMessage): String { + suspend fun prepareMessageV3(content: T, options: SendOptions? = null): String { return when (this) { - is V1 -> conversationV1.send(prepared = prepared) - is V2 -> conversationV2.send(prepared = prepared) - is Group -> throw XMTPException("Groups do not support prepared messages") // We return a encoded content not a prepared Message which requires a envelope + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.prepareMessage(content, options) + is Dm -> dm.prepareMessage(content, options) } } @@ -184,6 +161,7 @@ sealed class Conversation { is V1 -> conversationV1.send(content = content, options = options) is V2 -> conversationV2.send(content = content, options = options) is Group -> group.send(content = content, options = options) + is Dm -> dm.send(content = content, options = options) } } @@ -192,6 +170,7 @@ sealed class Conversation { is V1 -> conversationV1.send(text = text, sendOptions, sentAt) is V2 -> conversationV2.send(text = text, sendOptions, sentAt) is Group -> group.send(text) + is Dm -> dm.send(text) } } @@ -200,23 +179,18 @@ sealed class Conversation { is V1 -> conversationV1.send(encodedContent = encodedContent, options = options) is V2 -> conversationV2.send(encodedContent = encodedContent, options = options) is Group -> group.send(encodedContent = encodedContent) + is Dm -> dm.send(encodedContent = encodedContent) } } - val clientAddress: String - get() { - return client.address - } - - // Is the topic of the conversation depending on the version - val topic: String - get() { - return when (this) { - is V1 -> conversationV1.topic.description - is V2 -> conversationV2.topic - is Group -> group.topic - } + suspend fun sync() { + return when (this) { + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.sync() + is Dm -> dm.sync() } + } /** * This lists messages sent to the [Conversation]. @@ -261,6 +235,8 @@ sealed class Conversation { direction = direction, ) } + + is Dm -> dm.messages(limit, before, after, direction) } } @@ -274,19 +250,36 @@ sealed class Conversation { is V1 -> conversationV1.decryptedMessages(limit, before, after, direction) is V2 -> conversationV2.decryptedMessages(limit, before, after, direction) is Group -> group.decryptedMessages(limit, before, after, direction) + is Dm -> dm.decryptedMessages(limit, before, after, direction) } } - fun decrypt( - envelope: Envelope, - message: MessageV3? = null, + fun decryptV3( + message: MessageV3, ): DecryptedMessage { return when (this) { - is V1 -> conversationV1.decrypt(envelope) - is V2 -> conversationV2.decrypt(envelope) - is Group -> { - message?.decrypt() ?: throw XMTPException("Groups require message be passed") - } + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> message.decrypt() + is Dm -> message.decrypt() + } + } + + fun decodeV3(message: MessageV3): DecodedMessage { + return when (this) { + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> message.decode() + is Dm -> message.decode() + } + } + + suspend fun processMessage(envelopeBytes: ByteArray): MessageV3 { + return when (this) { + is V1 -> throw XMTPException("Only supported for V3") + is V2 -> throw XMTPException("Only supported for V3") + is Group -> group.processMessage(envelopeBytes) + is Dm -> dm.processMessage(envelopeBytes) } } @@ -296,6 +289,7 @@ sealed class Conversation { is V1 -> return null is V2 -> conversationV2.consentProof is Group -> return null + is Dm -> return null } } @@ -306,6 +300,7 @@ sealed class Conversation { is V1 -> conversationV1.client is V2 -> conversationV2.client is Group -> group.client + is Dm -> dm.client } } @@ -318,6 +313,7 @@ sealed class Conversation { is V1 -> conversationV1.streamMessages() is V2 -> conversationV2.streamMessages() is Group -> group.streamMessages() + is Dm -> dm.streamMessages() } } @@ -326,14 +322,159 @@ sealed class Conversation { is V1 -> conversationV1.streamDecryptedMessages() is V2 -> conversationV2.streamDecryptedMessages() is Group -> group.streamDecryptedMessages() + is Dm -> dm.streamDecryptedMessages() + } + } + + // ------- V1 V2 to be deprecated ------ + + fun decrypt( + envelope: Envelope, + ): DecryptedMessage { + return when (this) { + is V1 -> conversationV1.decrypt(envelope) + is V2 -> conversationV2.decrypt(envelope) + is Group -> throw XMTPException("Use decryptV3 instead") + is Dm -> throw XMTPException("Use decryptV3 instead") } } + fun decode(envelope: Envelope): DecodedMessage { + return when (this) { + is V1 -> conversationV1.decode(envelope) + is V2 -> conversationV2.decodeEnvelope(envelope) + is Group -> throw XMTPException("Use decodeV3 instead") + is Dm -> throw XMTPException("Use decodeV3 instead") + } + } + + // This is the address of the peer that I am talking to. + val peerAddress: String + get() { + return when (this) { + is V1 -> conversationV1.peerAddress + is V2 -> conversationV2.peerAddress + is Group -> runBlocking { group.peerInboxIds().joinToString(",") } + is Dm -> runBlocking { dm.peerInboxId() } + } + } + + val peerAddresses: List + get() { + return when (this) { + is V1 -> listOf(conversationV1.peerAddress) + is V2 -> listOf(conversationV2.peerAddress) + is Group -> runBlocking { group.peerInboxIds() } + is Dm -> runBlocking { listOf(dm.peerInboxId()) } + } + } + + // This distinctly identifies between two addresses. + // Note: this will be empty for older v1 conversations. + val conversationId: String? + get() { + return when (this) { + is V1 -> null + is V2 -> conversationV2.context.conversationId + is Group -> null + is Dm -> null + } + } + + val keyMaterial: ByteArray? + get() { + return when (this) { + is V1 -> null + is V2 -> conversationV2.keyMaterial + is Group -> null + is Dm -> null + } + } + + /** + * This method is to create a TopicData object + * @return [TopicData] that contains all the information about the Topic, the conversation + * context and the necessary encryption data for it. + */ + fun toTopicData(): TopicData { + val data = TopicData.newBuilder() + .setCreatedNs(createdAt.time * 1_000_000) + .setPeerAddress(peerAddress) + return when (this) { + is V1 -> data.build() + is V2 -> data.setInvitation( + Invitation.InvitationV1.newBuilder() + .setTopic(topic) + .setContext(conversationV2.context) + .setAes256GcmHkdfSha256( + Aes256gcmHkdfsha256.newBuilder() + .setKeyMaterial(conversationV2.keyMaterial.toByteString()), + ), + ).build() + + is Group -> throw XMTPException("Groups do not support topics") + is Dm -> throw XMTPException("DMs do not support topics") + } + } + + fun decodeOrNull(envelope: Envelope): DecodedMessage? { + return try { + decode(envelope) + } catch (e: Exception) { + Log.d("CONVERSATION", "discarding message that failed to decode", e) + null + } + } + + fun prepareMessage(content: T, options: SendOptions? = null): PreparedMessage { + return when (this) { + is V1 -> conversationV1.prepareMessage(content = content, options = options) + is V2 -> conversationV2.prepareMessage(content = content, options = options) + is Group -> throw XMTPException("Use prepareMessageV3 instead") + is Dm -> throw XMTPException("Use prepareMessageV3 instead") + } + } + + fun prepareMessage( + encodedContent: EncodedContent, + options: SendOptions? = null, + ): PreparedMessage { + return when (this) { + is V1 -> conversationV1.prepareMessage( + encodedContent = encodedContent, + options = options + ) + + is V2 -> conversationV2.prepareMessage( + encodedContent = encodedContent, + options = options + ) + + is Group -> throw XMTPException("Use prepareMessageV3 instead") + is Dm -> throw XMTPException("Use prepareMessageV3 instead") + } + } + + suspend fun send(prepared: PreparedMessage): String { + return when (this) { + is V1 -> conversationV1.send(prepared = prepared) + is V2 -> conversationV2.send(prepared = prepared) + is Group -> throw XMTPException("Groups do not support sending prepared messages call sync instead") + is Dm -> throw XMTPException("DMs do not support sending prepared messages call sync instead") + } + } + + val clientAddress: String + get() { + return client.address + } + fun streamEphemeral(): Flow { return when (this) { is V1 -> return conversationV1.streamEphemeral() is V2 -> return conversationV2.streamEphemeral() is Group -> throw XMTPException("Groups do not support ephemeral messages") + is Dm -> throw XMTPException("DMs do not support ephemeral messages") } } } diff --git a/library/src/main/java/org/xmtp/android/library/Conversations.kt b/library/src/main/java/org/xmtp/android/library/Conversations.kt index b213f8d1..039672e4 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversations.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversations.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch +import org.xmtp.android.library.ConsentState.Companion.toFfiConsentState import org.xmtp.android.library.GRPCApiClient.Companion.makeQueryRequest import org.xmtp.android.library.Util.Companion.envelopeFromFFi import org.xmtp.android.library.libxmtp.MessageV3 @@ -36,22 +37,24 @@ import org.xmtp.proto.keystore.api.v1.Keystore.GetConversationHmacKeysResponse.H import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData import org.xmtp.proto.message.contents.Contact import org.xmtp.proto.message.contents.Invitation +import uniffi.xmtpv3.FfiConsentState +import uniffi.xmtpv3.FfiConversation import uniffi.xmtpv3.FfiConversationCallback import uniffi.xmtpv3.FfiConversations import uniffi.xmtpv3.FfiCreateGroupOptions import uniffi.xmtpv3.FfiEnvelope -import uniffi.xmtpv3.FfiGroup +import uniffi.xmtpv3.FfiGroupPermissionsOptions import uniffi.xmtpv3.FfiListConversationsOptions +import uniffi.xmtpv3.FfiListMessagesOptions import uniffi.xmtpv3.FfiMessage import uniffi.xmtpv3.FfiMessageCallback +import uniffi.xmtpv3.FfiPermissionPolicySet import uniffi.xmtpv3.FfiV2SubscribeRequest import uniffi.xmtpv3.FfiV2Subscription import uniffi.xmtpv3.FfiV2SubscriptionCallback import uniffi.xmtpv3.NoPointer import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.GroupPermissionPreconfiguration import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionPolicySet -import uniffi.xmtpv3.FfiGroupPermissionsOptions -import uniffi.xmtpv3.FfiPermissionPolicySet import java.util.Date import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.DurationUnit @@ -66,42 +69,19 @@ data class Conversations( private const val TAG = "CONVERSATIONS" } - /** - * This method creates a new conversation from an invitation. - * @param envelope Object that contains the information of the current [Client] such as topic - * and timestamp. - * @return [Conversation] from an invitation suing the current [Client]. - */ - fun fromInvite(envelope: Envelope): Conversation { - val sealedInvitation = Invitation.SealedInvitation.parseFrom(envelope.message) - val unsealed = sealedInvitation.v1.getInvitation(viewer = client.keys) - return Conversation.V2( - ConversationV2.create( - client = client, - invitation = unsealed, - header = sealedInvitation.v1.header, - ), - ) + enum class ConversationOrder { + CREATED_AT, + LAST_MESSAGE; } - /** - * This method creates a new conversation from an Intro. - * @param envelope Object that contains the information of the current [Client] such as topic - * and timestamp. - * @return [Conversation] from an Intro suing the current [Client]. - */ - fun fromIntro(envelope: Envelope): Conversation { - val messageV1 = MessageV1Builder.buildFromBytes(envelope.message.toByteArray()) - val senderAddress = messageV1.header.sender.walletAddress - val recipientAddress = messageV1.header.recipient.walletAddress - val peerAddress = if (client.address == senderAddress) recipientAddress else senderAddress - return Conversation.V1( - ConversationV1( - client = client, - peerAddress = peerAddress, - sentAt = messageV1.sentAt, - ), - ) + suspend fun conversationFromWelcome(envelopeBytes: ByteArray): Conversation { + val conversation = libXMTPConversations?.processStreamedWelcomeMessage(envelopeBytes) + ?: throw XMTPException("Client does not support Groups") + if (conversation.groupMetadata().conversationType() == "dm") { + return Conversation.Dm(Dm(client, conversation)) + } else { + return Conversation.Group(Group(client, conversation)) + } } suspend fun fromWelcome(envelopeBytes: ByteArray): Group { @@ -187,46 +167,48 @@ data class Conversations( } // Sync from the network the latest list of groups + @Deprecated("Sync now includes DMs and Groups", replaceWith = ReplaceWith("syncConversations")) suspend fun syncGroups() { libXMTPConversations?.sync() } - // Sync all existing local groups data from the network (Note: call syncGroups() first to get the latest list of groups) - suspend fun syncAllGroups(): UInt? { - return libXMTPConversations?.syncAllGroups() + // Sync from the network the latest list of conversations + suspend fun syncConversations() { + libXMTPConversations?.sync() } - suspend fun listGroups( - after: Date? = null, - before: Date? = null, - limit: Int? = null, - ): List { - return libXMTPConversations?.list( - opts = FfiListConversationsOptions( - after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - limit?.toLong() - ) - )?.map { - Group(client, it) - } ?: emptyList() + // Sync all existing local conversation data from the network (Note: call syncConversations() first to get the latest list of conversations) + suspend fun syncAllConversations(): UInt? { + return libXMTPConversations?.syncAllConversations() } - private suspend fun handleConsentProof( - consentProof: Invitation.ConsentProofPayload, - peerAddress: String, - ) { - val signature = consentProof.signature - val timestamp = consentProof.timestamp + // Sync all existing local groups data from the network (Note: call syncGroups() first to get the latest list of groups) + @Deprecated( + "Sync now includes DMs and Groups", + replaceWith = ReplaceWith("syncAllConversations") + ) + suspend fun syncAllGroups(): UInt? { + return libXMTPConversations?.syncAllConversations() + } - if (!KeyUtil.validateConsentSignature(signature, client.address, peerAddress, timestamp)) { - return + suspend fun findOrCreateDm(peerAddress: String): Dm { + if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") + if (peerAddress.lowercase() == client.address.lowercase()) { + throw XMTPException("Recipient is sender") } - val contacts = client.contacts - contacts.refreshConsentList() - if (contacts.consentList.state(peerAddress) == ConsentState.UNKNOWN) { - contacts.allow(listOf(peerAddress)) + val falseAddresses = + client.canMessageV3(listOf(peerAddress)).filter { !it.value }.map { it.key } + if (falseAddresses.isNotEmpty()) { + throw XMTPException("${falseAddresses.joinToString()} not on network") + } + var dm = client.findDm(peerAddress) + if (dm == null) { + val dmConversation = libXMTPConversations?.createDm(peerAddress.lowercase()) + ?: throw XMTPException("Client does not support V3 Dms") + dm = Dm(client, dmConversation) + client.contacts.allowGroups(groupIds = listOf(dm.id)) } + return dm } /** @@ -320,6 +302,106 @@ data class Conversations( return conversation } + suspend fun listGroups( + after: Date? = null, + before: Date? = null, + limit: Int? = null, + ): List { + val ffiGroups = libXMTPConversations?.listGroups( + opts = FfiListConversationsOptions( + after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + limit?.toLong() + ) + ) ?: throw XMTPException("Client does not support V3 dms") + + return ffiGroups.map { + Group(client, it) + } + } + + suspend fun listDms( + after: Date? = null, + before: Date? = null, + limit: Int? = null, + ): List { + if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") + val ffiDms = libXMTPConversations?.listDms( + opts = FfiListConversationsOptions( + after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + limit?.toLong() + ) + ) ?: throw XMTPException("Client does not support V3 dms") + + return ffiDms.map { + Dm(client, it) + } + } + + suspend fun listConversations( + after: Date? = null, + before: Date? = null, + limit: Int? = null, + order: ConversationOrder = ConversationOrder.CREATED_AT, + consentState: ConsentState? = null, + ): List { + if (client.hasV2Client) + throw XMTPException("Only supported for V3 only clients.") + + val ffiConversations = libXMTPConversations?.list( + FfiListConversationsOptions( + after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + limit?.toLong() + ) + ) ?: throw XMTPException("Client does not support V3 dms") + + val filteredConversations = filterByConsentState(ffiConversations, consentState) + val sortedConversations = sortConversations(filteredConversations, order) + + return filteredConversations.map { it.toConversation() } + } + + private fun sortConversations( + conversations: List, + order: ConversationOrder, + ): List { + return when (order) { + ConversationOrder.LAST_MESSAGE -> { + conversations.map { conversation -> + val message = + conversation.findMessages(FfiListMessagesOptions(null, null, null, null)) + .lastOrNull() + conversation to message?.sentAtNs + }.sortedByDescending { + it.second ?: 0L + }.map { + it.first + } + } + ConversationOrder.CREATED_AT -> conversations + } + } + + private fun filterByConsentState( + conversations: List, + consentState: ConsentState?, + ): List { + return consentState?.let { state -> + conversations.filter { it.consentState() == toFfiConsentState(state) } + } ?: conversations + } + + private fun FfiConversation.toConversation(): Conversation { + return if (groupMetadata().conversationType() == "dm") { + Conversation.Dm(Dm(client, this)) + } else { + Conversation.Group(Group(client, this)) + } + } + + /** * Get the list of conversations that current user has * @return The list of [Conversation] that the current [Client] has. @@ -359,42 +441,13 @@ data class Conversations( }.map { Pair(it.topic, it) } if (includeGroups) { - syncGroups() + syncConversations() val groups = listGroups() - conversationsByTopic += groups.map { Pair(it.id.toString(), Conversation.Group(it)) } + conversationsByTopic += groups.map { Pair(it.topic, Conversation.Group(it)) } } return conversationsByTopic.values.sortedByDescending { it.createdAt } } - fun importTopicData(data: TopicData): Conversation { - val conversation: Conversation - if (!data.hasInvitation()) { - val sentAt = Date(data.createdNs / 1_000_000) - conversation = Conversation.V1( - ConversationV1( - client, - data.peerAddress, - sentAt, - ), - ) - } else { - conversation = Conversation.V2( - ConversationV2( - topic = data.invitation.topic, - keyMaterial = data.invitation.aes256GcmHkdfSha256.keyMaterial.toByteArray(), - context = data.invitation.context, - peerAddress = data.peerAddress, - client = client, - createdAtNs = data.createdNs, - header = Invitation.SealedInvitationHeaderV1.getDefaultInstance(), - consentProof = if (data.invitation.hasConsentProof()) data.invitation.consentProof else null - ), - ) - } - conversationsByTopic[conversation.topic] = conversation - return conversation - } - fun getHmacKeys( request: Keystore.GetConversationHmacKeysRequest? = null, ): Keystore.GetConversationHmacKeysResponse { @@ -433,73 +486,208 @@ data class Conversations( return hmacKeysResponse.build() } - private suspend fun listIntroductionPeers(pagination: Pagination? = null): Map { - val apiClient = client.apiClient ?: throw XMTPException("V2 only function") - val envelopes = apiClient.queryTopic( - topic = Topic.userIntro(client.address), - pagination = pagination, - ).envelopesList - val messages = envelopes.mapNotNull { envelope -> - try { - val message = MessageV1Builder.buildFromBytes(envelope.message.toByteArray()) - // Attempt to decrypt, just to make sure we can - message.decrypt(client.privateKeyBundleV1) - message - } catch (e: Exception) { - Log.d(TAG, e.message.toString()) - null - } + private suspend fun handleConsentProof( + consentProof: Invitation.ConsentProofPayload, + peerAddress: String, + ) { + val signature = consentProof.signature + val timestamp = consentProof.timestamp + + if (!KeyUtil.validateConsentSignature(signature, client.address, peerAddress, timestamp)) { + return } - val seenPeers: MutableMap = mutableMapOf() - for (message in messages) { - val recipientAddress = message.recipientAddress - val senderAddress = message.senderAddress - val sentAt = message.sentAt - val peerAddress = - if (recipientAddress == client.address) senderAddress else recipientAddress - val existing = seenPeers[peerAddress] - if (existing == null) { - seenPeers[peerAddress] = sentAt - continue - } - if (existing > sentAt) { - seenPeers[peerAddress] = sentAt - } + val contacts = client.contacts + contacts.refreshConsentList() + if (contacts.consentList.state(peerAddress) == ConsentState.UNKNOWN) { + contacts.allow(listOf(peerAddress)) } - return seenPeers } - /** - * Get the list of invitations using the data sent [pagination] - * @param pagination Information of the topics, ranges (dates), etc. - * @return List of [SealedInvitation] that are inside of the range specified by [pagination] - */ - private suspend fun listInvitations(pagination: Pagination? = null): List { - val apiClient = client.apiClient ?: throw XMTPException("V2 only function") - val envelopes = - apiClient.envelopes(Topic.userInvite(client.address).description, pagination) - return envelopes.map { envelope -> - SealedInvitation.parseFrom(envelope.message) + fun stream(): Flow = callbackFlow { + val streamedConversationTopics: MutableSet = mutableSetOf() + val subscriptionCallback = object : FfiV2SubscriptionCallback { + override fun onMessage(message: FfiEnvelope) { + val envelope = envelopeFromFFi(message) + if (envelope.contentTopic == Topic.userIntro(client.address).description) { + val conversationV1 = fromIntro(envelope = envelope) + if (!streamedConversationTopics.contains(conversationV1.topic)) { + streamedConversationTopics.add(conversationV1.topic) + trySend(conversationV1) + } + } + + if (envelope.contentTopic == Topic.userInvite(client.address).description) { + val conversationV2 = fromInvite(envelope = envelope) + if (!streamedConversationTopics.contains(conversationV2.topic)) { + streamedConversationTopics.add(conversationV2.topic) + trySend(conversationV2) + } + } + } } - } - fun conversation(sealedInvitation: SealedInvitation): ConversationV2 { - val unsealed = sealedInvitation.v1.getInvitation(viewer = client.keys) - return ConversationV2.create( - client = client, - invitation = unsealed, - header = sealedInvitation.v1.header, + val stream = client.subscribe2( + FfiV2SubscribeRequest( + listOf( + Topic.userIntro(client.address).description, + Topic.userInvite(client.address).description + ) + ), + subscriptionCallback ) + + awaitClose { launch { stream.end() } } } - /** - * @return This lists messages sent to the [Conversation]. - * This pulls messages from multiple conversations in a single call. - * @see Conversation.messages - */ - suspend fun listBatchMessages( - topics: List>, + fun streamAll(): Flow { + return merge(streamGroupConversations(), stream()) + } + + fun streamConversations(): Flow = callbackFlow { + if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") + val conversationCallback = object : FfiConversationCallback { + override fun onConversation(conversation: FfiConversation) { + if (conversation.groupMetadata().conversationType() == "dm") { + trySend(Conversation.Dm(Dm(client, conversation))) + } else { + trySend(Conversation.Group(Group(client, conversation))) + } + } + } + + val stream = libXMTPConversations?.stream(conversationCallback) + ?: throw XMTPException("Client does not support V3") + + awaitClose { stream.end() } + } + + fun streamGroups(): Flow = callbackFlow { + val groupCallback = object : FfiConversationCallback { + override fun onConversation(conversation: FfiConversation) { + trySend(Group(client, conversation)) + } + } + val stream = libXMTPConversations?.streamGroups(groupCallback) + ?: throw XMTPException("Client does not support Groups") + awaitClose { stream.end() } + } + + fun streamAllMessages(includeGroups: Boolean = false): Flow { + return if (includeGroups) { + merge(streamAllV2Messages(), streamAllGroupMessages()) + } else { + streamAllV2Messages() + } + } + + fun streamAllDecryptedMessages(includeGroups: Boolean = false): Flow { + return if (includeGroups) { + merge(streamAllV2DecryptedMessages(), streamAllGroupDecryptedMessages()) + } else { + streamAllV2DecryptedMessages() + } + } + + fun streamAllGroupMessages(): Flow = callbackFlow { + val messageCallback = object : FfiMessageCallback { + override fun onMessage(message: FfiMessage) { + val decodedMessage = MessageV3(client, message).decodeOrNull() + decodedMessage?.let { + trySend(it) + } + } + } + val stream = libXMTPConversations?.streamAllGroupMessages(messageCallback) + ?: throw XMTPException("Client does not support Groups") + awaitClose { stream.end() } + } + + fun streamAllGroupDecryptedMessages(): Flow = callbackFlow { + val messageCallback = object : FfiMessageCallback { + override fun onMessage(message: FfiMessage) { + val decryptedMessage = MessageV3(client, message).decryptOrNull() + decryptedMessage?.let { + trySend(it) + } + } + } + val stream = libXMTPConversations?.streamAllGroupMessages(messageCallback) + ?: throw XMTPException("Client does not support Groups") + awaitClose { stream.end() } + } + + fun streamAllConversationMessages(): Flow = callbackFlow { + if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") + val messageCallback = object : FfiMessageCallback { + override fun onMessage(message: FfiMessage) { + val conversation = client.findConversation(message.convoId.toHex()) + val decodedMessage = MessageV3(client, message).decodeOrNull() + when (conversation?.version) { + Conversation.Version.DM -> { + decodedMessage?.let { trySend(it) } + } + + else -> { + decodedMessage?.let { trySend(it) } + } + } + } + } + + val stream = libXMTPConversations?.streamAllMessages(messageCallback) + ?: throw XMTPException("Client does not support Groups") + + awaitClose { stream.end() } + } + + fun streamAllConversationDecryptedMessages(): Flow = callbackFlow { + if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") + val messageCallback = object : FfiMessageCallback { + override fun onMessage(message: FfiMessage) { + val conversation = client.findConversation(message.convoId.toHex()) + val decryptedMessage = MessageV3(client, message).decryptOrNull() + + when (conversation?.version) { + Conversation.Version.DM -> { + decryptedMessage?.let { trySend(it) } + } + + else -> { + decryptedMessage?.let { trySend(it) } + } + } + } + } + + val stream = libXMTPConversations?.streamAllMessages(messageCallback) + ?: throw XMTPException("Client does not support Groups") + + awaitClose { stream.end() } + } + + private fun streamGroupConversations(): Flow = callbackFlow { + val groupCallback = object : FfiConversationCallback { + override fun onConversation(conversation: FfiConversation) { + trySend(Conversation.Group(Group(client, conversation))) + } + } + val stream = libXMTPConversations?.streamGroups(groupCallback) + ?: throw XMTPException("Client does not support Groups") + awaitClose { stream.end() } + } + + // ------- V1 V2 to be deprecated ------ + + /** + * @return This lists messages sent to the [Conversation]. + * This pulls messages from multiple conversations in a single call. + * @see Conversation.messages + */ + suspend fun listBatchMessages( + topics: List>, ): List { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. The local database handles persistence of messages. Use listConversations order lastMessage") + val requests = topics.map { (topic, page) -> makeQueryRequest(topic = topic, pagination = page) } @@ -534,6 +722,8 @@ data class Conversations( suspend fun listBatchDecryptedMessages( topics: List>, ): List { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. The local database handles persistence of messages. Use listConversations order lastMessage") + val requests = topics.map { (topic, page) -> makeQueryRequest(topic = topic, pagination = page) } @@ -565,6 +755,86 @@ data class Conversations( return messages } + fun importTopicData(data: TopicData): Conversation { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. The local database handles persistence.") + val conversation: Conversation + if (!data.hasInvitation()) { + val sentAt = Date(data.createdNs / 1_000_000) + conversation = Conversation.V1( + ConversationV1( + client, + data.peerAddress, + sentAt, + ), + ) + } else { + conversation = Conversation.V2( + ConversationV2( + topic = data.invitation.topic, + keyMaterial = data.invitation.aes256GcmHkdfSha256.keyMaterial.toByteArray(), + context = data.invitation.context, + peerAddress = data.peerAddress, + client = client, + createdAtNs = data.createdNs, + header = Invitation.SealedInvitationHeaderV1.getDefaultInstance(), + consentProof = if (data.invitation.hasConsentProof()) data.invitation.consentProof else null + ), + ) + } + conversationsByTopic[conversation.topic] = conversation + return conversation + } + + /** + * This method creates a new conversation from an invitation. + * @param envelope Object that contains the information of the current [Client] such as topic + * and timestamp. + * @return [Conversation] from an invitation suing the current [Client]. + */ + fun fromInvite(envelope: Envelope): Conversation { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. Use conversationFromWelcome.") + val sealedInvitation = Invitation.SealedInvitation.parseFrom(envelope.message) + val unsealed = sealedInvitation.v1.getInvitation(viewer = client.keys) + return Conversation.V2( + ConversationV2.create( + client = client, + invitation = unsealed, + header = sealedInvitation.v1.header, + ), + ) + } + + /** + * This method creates a new conversation from an Intro. + * @param envelope Object that contains the information of the current [Client] such as topic + * and timestamp. + * @return [Conversation] from an Intro suing the current [Client]. + */ + fun fromIntro(envelope: Envelope): Conversation { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. Use conversationFromWelcome.") + val messageV1 = MessageV1Builder.buildFromBytes(envelope.message.toByteArray()) + val senderAddress = messageV1.header.sender.walletAddress + val recipientAddress = messageV1.header.recipient.walletAddress + val peerAddress = if (client.address == senderAddress) recipientAddress else senderAddress + return Conversation.V1( + ConversationV1( + client = client, + peerAddress = peerAddress, + sentAt = messageV1.sentAt, + ), + ) + } + + fun conversation(sealedInvitation: SealedInvitation): ConversationV2 { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. Use client.findDm to find the dm.") + val unsealed = sealedInvitation.v1.getInvitation(viewer = client.keys) + return ConversationV2.create( + client = client, + invitation = unsealed, + header = sealedInvitation.v1.header, + ) + } + /** * Send an invitation from the current [Client] to the specified recipient (Client) * @param recipient The public key of the client that you want to send the invitation @@ -577,6 +847,7 @@ data class Conversations( invitation: InvitationV1, created: Date, ): SealedInvitation { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. Use newConversation to create welcome.") client.keys.let { val sealed = SealedInvitationBuilder.buildFromV1( sender = it, @@ -608,101 +879,6 @@ data class Conversations( } } - /** - * This subscribes the current [Client] to a topic as userIntro and userInvite and returns a flow - * of the information of those conversations according to the topics - * @return Stream of data information for the conversations - */ - fun stream(): Flow = callbackFlow { - val streamedConversationTopics: MutableSet = mutableSetOf() - val subscriptionCallback = object : FfiV2SubscriptionCallback { - override fun onMessage(message: FfiEnvelope) { - val envelope = envelopeFromFFi(message) - if (envelope.contentTopic == Topic.userIntro(client.address).description) { - val conversationV1 = fromIntro(envelope = envelope) - if (!streamedConversationTopics.contains(conversationV1.topic)) { - streamedConversationTopics.add(conversationV1.topic) - trySend(conversationV1) - } - } - - if (envelope.contentTopic == Topic.userInvite(client.address).description) { - val conversationV2 = fromInvite(envelope = envelope) - if (!streamedConversationTopics.contains(conversationV2.topic)) { - streamedConversationTopics.add(conversationV2.topic) - trySend(conversationV2) - } - } - } - } - - val stream = client.subscribe2( - FfiV2SubscribeRequest( - listOf( - Topic.userIntro(client.address).description, - Topic.userInvite(client.address).description - ) - ), - subscriptionCallback - ) - - awaitClose { launch { stream.end() } } - } - - fun streamAll(): Flow { - return merge(streamGroupConversations(), stream()) - } - - private fun streamGroupConversations(): Flow = callbackFlow { - val groupCallback = object : FfiConversationCallback { - override fun onConversation(conversation: FfiGroup) { - trySend(Conversation.Group(Group(client, conversation))) - } - } - val stream = libXMTPConversations?.stream(groupCallback) - ?: throw XMTPException("Client does not support Groups") - awaitClose { stream.end() } - } - - fun streamGroups(): Flow = callbackFlow { - val groupCallback = object : FfiConversationCallback { - override fun onConversation(conversation: FfiGroup) { - trySend(Group(client, conversation)) - } - } - val stream = libXMTPConversations?.stream(groupCallback) - ?: throw XMTPException("Client does not support Groups") - awaitClose { stream.end() } - } - - fun streamAllGroupMessages(): Flow = callbackFlow { - val messageCallback = object : FfiMessageCallback { - override fun onMessage(message: FfiMessage) { - val decodedMessage = MessageV3(client, message).decodeOrNull() - decodedMessage?.let { - trySend(it) - } - } - } - val stream = libXMTPConversations?.streamAllMessages(messageCallback) - ?: throw XMTPException("Client does not support Groups") - awaitClose { stream.end() } - } - - fun streamAllGroupDecryptedMessages(): Flow = callbackFlow { - val messageCallback = object : FfiMessageCallback { - override fun onMessage(message: FfiMessage) { - val decryptedMessage = MessageV3(client, message).decryptOrNull() - decryptedMessage?.let { - trySend(it) - } - } - } - val stream = libXMTPConversations?.streamAllMessages(messageCallback) - ?: throw XMTPException("Client does not support Groups") - awaitClose { stream.end() } - } - /** * Get the stream of all messages of the current [Client] * @return Flow object of [DecodedMessage] that represents all the messages of the @@ -758,22 +934,6 @@ data class Conversations( awaitClose { launch { stream.end() } } } - fun streamAllMessages(includeGroups: Boolean = false): Flow { - return if (includeGroups) { - merge(streamAllV2Messages(), streamAllGroupMessages()) - } else { - streamAllV2Messages() - } - } - - fun streamAllDecryptedMessages(includeGroups: Boolean = false): Flow { - return if (includeGroups) { - merge(streamAllV2DecryptedMessages(), streamAllGroupDecryptedMessages()) - } else { - streamAllV2DecryptedMessages() - } - } - private fun streamAllV2DecryptedMessages(): Flow = callbackFlow { val topics = mutableListOf( Topic.userInvite(client.address).description, @@ -823,4 +983,56 @@ data class Conversations( awaitClose { launch { stream.end() } } } + + /** + * Get the list of invitations using the data sent [pagination] + * @param pagination Information of the topics, ranges (dates), etc. + * @return List of [SealedInvitation] that are inside of the range specified by [pagination] + */ + private suspend fun listInvitations(pagination: Pagination? = null): List { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. Use conversationFromWelcome.") + val apiClient = client.apiClient ?: throw XMTPException("V2 only function") + val envelopes = + apiClient.envelopes(Topic.userInvite(client.address).description, pagination) + return envelopes.map { envelope -> + SealedInvitation.parseFrom(envelope.message) + } + } + + private suspend fun listIntroductionPeers(pagination: Pagination? = null): Map { + if (!client.hasV2Client) throw XMTPException("Not supported for V3. Use conversationFromWelcome.") + val apiClient = client.apiClient ?: throw XMTPException("V2 only function") + val envelopes = apiClient.queryTopic( + topic = Topic.userIntro(client.address), + pagination = pagination, + ).envelopesList + val messages = envelopes.mapNotNull { envelope -> + try { + val message = MessageV1Builder.buildFromBytes(envelope.message.toByteArray()) + // Attempt to decrypt, just to make sure we can + message.decrypt(client.privateKeyBundleV1) + message + } catch (e: Exception) { + Log.d(TAG, e.message.toString()) + null + } + } + val seenPeers: MutableMap = mutableMapOf() + for (message in messages) { + val recipientAddress = message.recipientAddress + val senderAddress = message.senderAddress + val sentAt = message.sentAt + val peerAddress = + if (recipientAddress == client.address) senderAddress else recipientAddress + val existing = seenPeers[peerAddress] + if (existing == null) { + seenPeers[peerAddress] = sentAt + continue + } + if (existing > sentAt) { + seenPeers[peerAddress] = sentAt + } + } + return seenPeers + } } diff --git a/library/src/main/java/org/xmtp/android/library/Dm.kt b/library/src/main/java/org/xmtp/android/library/Dm.kt new file mode 100644 index 00000000..b3359c8d --- /dev/null +++ b/library/src/main/java/org/xmtp/android/library/Dm.kt @@ -0,0 +1,267 @@ +package org.xmtp.android.library + +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import org.xmtp.android.library.codecs.ContentCodec +import org.xmtp.android.library.codecs.EncodedContent +import org.xmtp.android.library.codecs.compress +import org.xmtp.android.library.libxmtp.Member +import org.xmtp.android.library.libxmtp.MessageV3 +import org.xmtp.android.library.messages.DecryptedMessage +import org.xmtp.android.library.messages.MessageDeliveryStatus +import org.xmtp.android.library.messages.PagingInfoSortDirection +import org.xmtp.android.library.messages.Topic +import org.xmtp.proto.message.api.v1.MessageApiOuterClass +import uniffi.xmtpv3.FfiConversation +import uniffi.xmtpv3.FfiConversationMetadata +import uniffi.xmtpv3.FfiDeliveryStatus +import uniffi.xmtpv3.FfiListMessagesOptions +import uniffi.xmtpv3.FfiMessage +import uniffi.xmtpv3.FfiMessageCallback +import java.util.Date +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.DurationUnit + +class Dm(val client: Client, private val libXMTPGroup: FfiConversation) { + val id: String + get() = libXMTPGroup.id().toHex() + + val topic: String + get() = Topic.groupMessage(id).description + + val createdAt: Date + get() = Date(libXMTPGroup.createdAtNs() / 1_000_000) + + private val metadata: FfiConversationMetadata + get() = libXMTPGroup.groupMetadata() + + val name: String + get() = libXMTPGroup.groupName() + + val imageUrlSquare: String + get() = libXMTPGroup.groupImageUrlSquare() + + val description: String + get() = libXMTPGroup.groupDescription() + + val pinnedFrameUrl: String + get() = libXMTPGroup.groupPinnedFrameUrl() + + suspend fun send(text: String): String { + return send(encodeContent(content = text, options = null)) + } + + suspend fun send(content: T, options: SendOptions? = null): String { + val preparedMessage = encodeContent(content = content, options = options) + return send(preparedMessage) + } + + suspend fun send(encodedContent: EncodedContent): String { + if (consentState() == ConsentState.UNKNOWN) { + updateConsentState(ConsentState.ALLOWED) + } + val messageId = libXMTPGroup.send(contentBytes = encodedContent.toByteArray()) + return messageId.toHex() + } + + fun encodeContent(content: T, options: SendOptions?): EncodedContent { + val codec = Client.codecRegistry.find(options?.contentType) + + fun > encode(codec: Codec, content: Any?): EncodedContent { + val contentType = content as? T + if (contentType != null) { + return codec.encode(contentType) + } else { + throw XMTPException("Codec type is not registered") + } + } + + var encoded = encode(codec = codec as ContentCodec, content = content) + val fallback = codec.fallback(content) + if (!fallback.isNullOrBlank()) { + encoded = encoded.toBuilder().also { + it.fallback = fallback + }.build() + } + val compression = options?.compression + if (compression != null) { + encoded = encoded.compress(compression) + } + return encoded + } + + suspend fun prepareMessage(content: T, options: SendOptions? = null): String { + if (consentState() == ConsentState.UNKNOWN) { + updateConsentState(ConsentState.ALLOWED) + } + val encodeContent = encodeContent(content = content, options = options) + return libXMTPGroup.sendOptimistic(encodeContent.toByteArray()).toHex() + } + + suspend fun publishMessages() { + libXMTPGroup.publishMessages() + } + + suspend fun sync() { + libXMTPGroup.sync() + } + + fun messages( + limit: Int? = null, + before: Date? = null, + after: Date? = null, + direction: PagingInfoSortDirection = MessageApiOuterClass.SortDirection.SORT_DIRECTION_DESCENDING, + deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL, + ): List { + val messages = libXMTPGroup.findMessages( + opts = FfiListMessagesOptions( + sentBeforeNs = before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + limit = limit?.toLong(), + deliveryStatus = when (deliveryStatus) { + MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED + MessageDeliveryStatus.UNPUBLISHED -> FfiDeliveryStatus.UNPUBLISHED + MessageDeliveryStatus.FAILED -> FfiDeliveryStatus.FAILED + else -> null + } + ) + ).mapNotNull { + MessageV3(client, it).decodeOrNull() + } + + return when (direction) { + MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages + else -> messages.reversed() + } + } + + fun decryptedMessages( + limit: Int? = null, + before: Date? = null, + after: Date? = null, + direction: PagingInfoSortDirection = MessageApiOuterClass.SortDirection.SORT_DIRECTION_DESCENDING, + deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL, + ): List { + val messages = libXMTPGroup.findMessages( + opts = FfiListMessagesOptions( + sentBeforeNs = before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + limit = limit?.toLong(), + deliveryStatus = when (deliveryStatus) { + MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED + MessageDeliveryStatus.UNPUBLISHED -> FfiDeliveryStatus.UNPUBLISHED + MessageDeliveryStatus.FAILED -> FfiDeliveryStatus.FAILED + else -> null + } + ) + ).mapNotNull { + MessageV3(client, it).decryptOrNull() + } + + return when (direction) { + MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages + else -> messages.reversed() + } + } + + suspend fun processMessage(envelopeBytes: ByteArray): MessageV3 { + val message = libXMTPGroup.processStreamedConversationMessage(envelopeBytes) + return MessageV3(client, message) + } + + fun creatorInboxId(): String { + return metadata.creatorInboxId() + } + + fun isCreator(): Boolean { + return metadata.creatorInboxId() == client.inboxId + } + + suspend fun members(): List { + return libXMTPGroup.listMembers().map { Member(it) } + } + + suspend fun peerInboxId(): String { + val ids = members().map { it.inboxId }.toMutableList() + ids.remove(client.inboxId) + return ids.first() + } + + suspend fun updateName(name: String) { + try { + return libXMTPGroup.updateGroupName(name) + } catch (e: Exception) { + throw XMTPException("Permission denied: Unable to update group name", e) + } + } + + suspend fun updateImageUrlSquare(imageUrl: String) { + try { + return libXMTPGroup.updateGroupImageUrlSquare(imageUrl) + } catch (e: Exception) { + throw XMTPException("Permission denied: Unable to update image url", e) + } + } + + suspend fun updateDescription(description: String) { + try { + return libXMTPGroup.updateGroupDescription(description) + } catch (e: Exception) { + throw XMTPException("Permission denied: Unable to update group description", e) + } + } + + suspend fun updatePinnedFrameUrl(pinnedFrameUrl: String) { + try { + return libXMTPGroup.updateGroupPinnedFrameUrl(pinnedFrameUrl) + } catch (e: Exception) { + throw XMTPException("Permission denied: Unable to update pinned frame", e) + } + } + + fun streamMessages(): Flow = callbackFlow { + val messageCallback = object : FfiMessageCallback { + override fun onMessage(message: FfiMessage) { + val decodedMessage = MessageV3(client, message).decodeOrNull() + decodedMessage?.let { + trySend(it) + } + } + } + + val stream = libXMTPGroup.stream(messageCallback) + awaitClose { stream.end() } + } + + fun streamDecryptedMessages(): Flow = callbackFlow { + val messageCallback = object : FfiMessageCallback { + override fun onMessage(message: FfiMessage) { + val decryptedMessage = MessageV3(client, message).decryptOrNull() + decryptedMessage?.let { + trySend(it) + } + } + } + + val stream = libXMTPGroup.stream(messageCallback) + awaitClose { stream.end() } + } + + suspend fun updateConsentState(state: ConsentState) { + if (client.hasV2Client) { + when (state) { + ConsentState.ALLOWED -> client.contacts.allowGroups(groupIds = listOf(id)) + ConsentState.DENIED -> client.contacts.denyGroups(groupIds = listOf(id)) + ConsentState.UNKNOWN -> Unit + } + } + + val consentState = ConsentState.toFfiConsentState(state) + libXMTPGroup.updateConsentState(consentState) + } + + fun consentState(): ConsentState { + return ConsentState.fromFfiConsentState(libXMTPGroup.consentState()) + } +} diff --git a/library/src/main/java/org/xmtp/android/library/Group.kt b/library/src/main/java/org/xmtp/android/library/Group.kt index 176a25f8..0fa3bfb3 100644 --- a/library/src/main/java/org/xmtp/android/library/Group.kt +++ b/library/src/main/java/org/xmtp/android/library/Group.kt @@ -14,8 +14,8 @@ import org.xmtp.android.library.messages.PagingInfoSortDirection import org.xmtp.android.library.messages.Topic import org.xmtp.proto.message.api.v1.MessageApiOuterClass import uniffi.xmtpv3.FfiDeliveryStatus -import uniffi.xmtpv3.FfiGroup -import uniffi.xmtpv3.FfiGroupMetadata +import uniffi.xmtpv3.FfiConversation +import uniffi.xmtpv3.FfiConversationMetadata import uniffi.xmtpv3.FfiGroupPermissions import uniffi.xmtpv3.FfiListMessagesOptions import uniffi.xmtpv3.FfiMessage @@ -28,7 +28,7 @@ import java.util.Date import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.DurationUnit -class Group(val client: Client, private val libXMTPGroup: FfiGroup) { +class Group(val client: Client, private val libXMTPGroup: FfiConversation) { val id: String get() = libXMTPGroup.id().toHex() @@ -38,7 +38,7 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) { val createdAt: Date get() = Date(libXMTPGroup.createdAtNs() / 1_000_000) - private val metadata: FfiGroupMetadata + private val metadata: FfiConversationMetadata get() = libXMTPGroup.groupMetadata() private val permissions: FfiGroupPermissions @@ -174,7 +174,7 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) { } suspend fun processMessage(envelopeBytes: ByteArray): MessageV3 { - val message = libXMTPGroup.processStreamedGroupMessage(envelopeBytes) + val message = libXMTPGroup.processStreamedConversationMessage(envelopeBytes) return MessageV3(client, message) } diff --git a/library/src/main/java/org/xmtp/android/library/SigningKey.kt b/library/src/main/java/org/xmtp/android/library/SigningKey.kt index 34bd986d..2febe027 100644 --- a/library/src/main/java/org/xmtp/android/library/SigningKey.kt +++ b/library/src/main/java/org/xmtp/android/library/SigningKey.kt @@ -19,9 +19,31 @@ import java.util.Date interface SigningKey { val address: String - suspend fun sign(data: ByteArray): SignatureOuterClass.Signature? + // If this signing key is a smart contract wallet + val isSmartContractWallet: Boolean + get() = false - suspend fun sign(message: String): SignatureOuterClass.Signature? + // The chainId of the Smart Contract Wallet value should be null if not SCW + var chainId: Long? + get() = null + set(_) {} + + // Default blockNumber value set to null + var blockNumber: Long? + get() = null + set(_) {} + + suspend fun sign(data: ByteArray): SignatureOuterClass.Signature? { + throw NotImplementedError("sign(ByteArray) is not implemented.") + } + + suspend fun sign(message: String): SignatureOuterClass.Signature? { + throw NotImplementedError("sign(String) is not implemented.") + } + + suspend fun signSCW(message: String): ByteArray { + throw NotImplementedError("signSCW(String) is not implemented.") + } } /** @@ -36,10 +58,13 @@ fun SigningKey.createIdentity( identity: PrivateKeyOuterClass.PrivateKey, preCreateIdentityCallback: PreEventCallback? = null, ): AuthorizedIdentity { - val slimKey = PublicKeyOuterClass.PublicKey.newBuilder().apply { - timestamp = Date().time - secp256K1Uncompressed = identity.publicKey.secp256K1Uncompressed - }.build() + val slimKey = + PublicKeyOuterClass.PublicKey + .newBuilder() + .apply { + timestamp = Date().time + secp256K1Uncompressed = identity.publicKey.secp256K1Uncompressed + }.build() preCreateIdentityCallback?.let { runBlocking { @@ -53,17 +78,19 @@ fun SigningKey.createIdentity( val signature = runBlocking { sign(signatureText) } ?: throw XMTPException("Illegal signature") val signatureData = KeyUtil.getSignatureData(signature.rawData.toByteString().toByteArray()) - val publicKey = Sign.recoverFromSignature( - BigInteger(1, signatureData.v).toInt(), - ECDSASignature(BigInteger(1, signatureData.r), BigInteger(1, signatureData.s)), - digest, - ) + val publicKey = + Sign.recoverFromSignature( + BigInteger(1, signatureData.v).toInt(), + ECDSASignature(BigInteger(1, signatureData.r), BigInteger(1, signatureData.s)), + digest, + ) - val authorized = PublicKey.newBuilder().also { - it.secp256K1Uncompressed = slimKey.secp256K1Uncompressed - it.timestamp = slimKey.timestamp - it.signature = signature - } + val authorized = + PublicKey.newBuilder().also { + it.secp256K1Uncompressed = slimKey.secp256K1Uncompressed + it.timestamp = slimKey.timestamp + it.signature = signature + } return AuthorizedIdentity( address = Keys.toChecksumAddress(Keys.getAddress(publicKey)), authorized = authorized.build(), diff --git a/library/src/main/java/org/xmtp/android/library/libxmtp/Member.kt b/library/src/main/java/org/xmtp/android/library/libxmtp/Member.kt index c502247a..c8ca6c85 100644 --- a/library/src/main/java/org/xmtp/android/library/libxmtp/Member.kt +++ b/library/src/main/java/org/xmtp/android/library/libxmtp/Member.kt @@ -1,13 +1,13 @@ package org.xmtp.android.library.libxmtp import org.xmtp.android.library.ConsentState -import uniffi.xmtpv3.FfiGroupMember +import uniffi.xmtpv3.FfiConversationMember import uniffi.xmtpv3.FfiPermissionLevel enum class PermissionLevel { MEMBER, ADMIN, SUPER_ADMIN } -class Member(private val ffiMember: FfiGroupMember) { +class Member(private val ffiMember: FfiConversationMember) { val inboxId: String get() = ffiMember.inboxId diff --git a/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt b/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt index fc1302b7..631fd2e5 100644 --- a/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt +++ b/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt @@ -11,7 +11,7 @@ import org.xmtp.android.library.messages.MessageDeliveryStatus import org.xmtp.android.library.messages.Topic import org.xmtp.android.library.toHex import uniffi.xmtpv3.FfiDeliveryStatus -import uniffi.xmtpv3.FfiGroupMessageKind +import uniffi.xmtpv3.FfiConversationMessageKind import uniffi.xmtpv3.FfiMessage import java.util.Date @@ -47,7 +47,7 @@ data class MessageV3(val client: Client, private val libXMTPMessage: FfiMessage) sent = sentAt, deliveryStatus = deliveryStatus ) - if (decodedMessage.encodedContent.type == ContentTypeGroupUpdated && libXMTPMessage.kind != FfiGroupMessageKind.MEMBERSHIP_CHANGE) { + if (decodedMessage.encodedContent.type == ContentTypeGroupUpdated && libXMTPMessage.kind != FfiConversationMessageKind.MEMBERSHIP_CHANGE) { throw XMTPException("Error decoding group membership change") } return decodedMessage diff --git a/library/src/main/java/xmtpv3.kt b/library/src/main/java/xmtpv3.kt index 7e7f10b1..9d67779b 100644 --- a/library/src/main/java/xmtpv3.kt +++ b/library/src/main/java/xmtpv3.kt @@ -860,221 +860,245 @@ internal interface UniffiLib : Library { } } - fun uniffi_xmtpv3_fn_clone_fficonversations( - `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, - ): Pointer - - fun uniffi_xmtpv3_fn_free_fficonversations( - `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, - ): Unit - - fun uniffi_xmtpv3_fn_method_fficonversations_create_dm( - `ptr`: Pointer, `accountAddress`: RustBuffer.ByValue, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_create_group( - `ptr`: Pointer, `accountAddresses`: RustBuffer.ByValue, `opts`: RustBuffer.ByValue, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_list( - `ptr`: Pointer, `opts`: RustBuffer.ByValue, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_process_streamed_welcome_message( - `ptr`: Pointer, `envelopeBytes`: RustBuffer.ByValue, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_stream( - `ptr`: Pointer, `callback`: Long, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_stream_all_messages( - `ptr`: Pointer, `messageCallback`: Long, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_sync( - `ptr`: Pointer, - ): Long - - fun uniffi_xmtpv3_fn_method_fficonversations_sync_all_groups( - `ptr`: Pointer, - ): Long - - fun uniffi_xmtpv3_fn_clone_ffigroup( + fun uniffi_xmtpv3_fn_clone_fficonversation( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer - fun uniffi_xmtpv3_fn_free_ffigroup( + fun uniffi_xmtpv3_fn_free_fficonversation( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_xmtpv3_fn_method_ffigroup_add_admin( + fun uniffi_xmtpv3_fn_method_fficonversation_add_admin( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_add_members( + fun uniffi_xmtpv3_fn_method_fficonversation_add_members( `ptr`: Pointer, `accountAddresses`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_add_members_by_inbox_id( + fun uniffi_xmtpv3_fn_method_fficonversation_add_members_by_inbox_id( `ptr`: Pointer, `inboxIds`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_add_super_admin( + fun uniffi_xmtpv3_fn_method_fficonversation_add_super_admin( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_added_by_inbox_id( + fun uniffi_xmtpv3_fn_method_fficonversation_added_by_inbox_id( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_admin_list( + fun uniffi_xmtpv3_fn_method_fficonversation_admin_list( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_consent_state( + fun uniffi_xmtpv3_fn_method_fficonversation_consent_state( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_created_at_ns( + fun uniffi_xmtpv3_fn_method_fficonversation_created_at_ns( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_find_messages( + fun uniffi_xmtpv3_fn_method_fficonversation_find_messages( `ptr`: Pointer, `opts`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_group_description( + fun uniffi_xmtpv3_fn_method_fficonversation_group_description( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_group_image_url_square( + fun uniffi_xmtpv3_fn_method_fficonversation_group_image_url_square( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_group_metadata( + fun uniffi_xmtpv3_fn_method_fficonversation_group_metadata( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer - fun uniffi_xmtpv3_fn_method_ffigroup_group_name( + fun uniffi_xmtpv3_fn_method_fficonversation_group_name( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_group_permissions( + fun uniffi_xmtpv3_fn_method_fficonversation_group_permissions( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer - fun uniffi_xmtpv3_fn_method_ffigroup_group_pinned_frame_url( + fun uniffi_xmtpv3_fn_method_fficonversation_group_pinned_frame_url( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_id( + fun uniffi_xmtpv3_fn_method_fficonversation_id( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_is_active( + fun uniffi_xmtpv3_fn_method_fficonversation_is_active( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Byte - fun uniffi_xmtpv3_fn_method_ffigroup_is_admin( + fun uniffi_xmtpv3_fn_method_fficonversation_is_admin( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): Byte - fun uniffi_xmtpv3_fn_method_ffigroup_is_super_admin( + fun uniffi_xmtpv3_fn_method_fficonversation_is_super_admin( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): Byte - fun uniffi_xmtpv3_fn_method_ffigroup_list_members( + fun uniffi_xmtpv3_fn_method_fficonversation_list_members( `ptr`: Pointer, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_process_streamed_group_message( + fun uniffi_xmtpv3_fn_method_fficonversation_process_streamed_conversation_message( `ptr`: Pointer, `envelopeBytes`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_publish_messages( + fun uniffi_xmtpv3_fn_method_fficonversation_publish_messages( `ptr`: Pointer, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_remove_admin( + fun uniffi_xmtpv3_fn_method_fficonversation_remove_admin( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_remove_members( + fun uniffi_xmtpv3_fn_method_fficonversation_remove_members( `ptr`: Pointer, `accountAddresses`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_remove_members_by_inbox_id( + fun uniffi_xmtpv3_fn_method_fficonversation_remove_members_by_inbox_id( `ptr`: Pointer, `inboxIds`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_remove_super_admin( + fun uniffi_xmtpv3_fn_method_fficonversation_remove_super_admin( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_send( + fun uniffi_xmtpv3_fn_method_fficonversation_send( `ptr`: Pointer, `contentBytes`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_send_optimistic( + fun uniffi_xmtpv3_fn_method_fficonversation_send_optimistic( `ptr`: Pointer, `contentBytes`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_stream( + fun uniffi_xmtpv3_fn_method_fficonversation_stream( `ptr`: Pointer, `messageCallback`: Long, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_super_admin_list( + fun uniffi_xmtpv3_fn_method_fficonversation_super_admin_list( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroup_sync( + fun uniffi_xmtpv3_fn_method_fficonversation_sync( `ptr`: Pointer, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_update_consent_state( + fun uniffi_xmtpv3_fn_method_fficonversation_update_consent_state( `ptr`: Pointer, `state`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_xmtpv3_fn_method_ffigroup_update_group_description( + fun uniffi_xmtpv3_fn_method_fficonversation_update_group_description( `ptr`: Pointer, `groupDescription`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_update_group_image_url_square( + fun uniffi_xmtpv3_fn_method_fficonversation_update_group_image_url_square( `ptr`: Pointer, `groupImageUrlSquare`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_update_group_name( + fun uniffi_xmtpv3_fn_method_fficonversation_update_group_name( `ptr`: Pointer, `groupName`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_update_group_pinned_frame_url( + fun uniffi_xmtpv3_fn_method_fficonversation_update_group_pinned_frame_url( `ptr`: Pointer, `pinnedFrameUrl`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffigroup_update_permission_policy( + fun uniffi_xmtpv3_fn_method_fficonversation_update_permission_policy( `ptr`: Pointer, `permissionUpdateType`: RustBuffer.ByValue, `permissionPolicyOption`: RustBuffer.ByValue, `metadataField`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_clone_ffigroupmetadata( + fun uniffi_xmtpv3_fn_clone_fficonversationmetadata( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer - fun uniffi_xmtpv3_fn_free_ffigroupmetadata( + fun uniffi_xmtpv3_fn_free_fficonversationmetadata( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_xmtpv3_fn_method_ffigroupmetadata_conversation_type( + fun uniffi_xmtpv3_fn_method_fficonversationmetadata_conversation_type( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffigroupmetadata_creator_inbox_id( + fun uniffi_xmtpv3_fn_method_fficonversationmetadata_creator_inbox_id( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_clone_fficonversations( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun uniffi_xmtpv3_fn_free_fficonversations( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_xmtpv3_fn_method_fficonversations_create_dm( + `ptr`: Pointer, `accountAddress`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_create_group( + `ptr`: Pointer, `accountAddresses`: RustBuffer.ByValue, `opts`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_list( + `ptr`: Pointer, `opts`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_list_dms( + `ptr`: Pointer, `opts`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_list_groups( + `ptr`: Pointer, `opts`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_process_streamed_welcome_message( + `ptr`: Pointer, `envelopeBytes`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream( + `ptr`: Pointer, `callback`: Long, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream_all_dm_messages( + `ptr`: Pointer, `messageCallback`: Long, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream_all_group_messages( + `ptr`: Pointer, `messageCallback`: Long, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream_all_messages( + `ptr`: Pointer, `messageCallback`: Long, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream_dms( + `ptr`: Pointer, `callback`: Long, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream_groups( + `ptr`: Pointer, `callback`: Long, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_sync( + `ptr`: Pointer, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_sync_all_conversations( + `ptr`: Pointer, + ): Long + fun uniffi_xmtpv3_fn_clone_ffigrouppermissions( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer @@ -1205,6 +1229,10 @@ internal interface UniffiLib : Library { `newWalletAddress`: RustBuffer.ByValue, ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_addresses_from_inbox_id( + `ptr`: Pointer, `refreshFromNetwork`: Byte, `inboxIds`: RustBuffer.ByValue, + ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_apply_signature_request( `ptr`: Pointer, `signatureRequest`: Pointer, ): Long @@ -1213,6 +1241,10 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `accountAddresses`: RustBuffer.ByValue, ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_conversation( + `ptr`: Pointer, `conversationId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_xmtpv3_fn_method_ffixmtpclient_conversations( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer @@ -1221,6 +1253,10 @@ internal interface UniffiLib : Library { `ptr`: Pointer, ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_dm_conversation( + `ptr`: Pointer, `targetInboxId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_xmtpv3_fn_method_ffixmtpclient_find_inbox_id( `ptr`: Pointer, `address`: RustBuffer.ByValue, ): Long @@ -1233,10 +1269,6 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, ): Long - fun uniffi_xmtpv3_fn_method_ffixmtpclient_group( - `ptr`: Pointer, `groupId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_xmtpv3_fn_method_ffixmtpclient_inbox_id( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue @@ -1667,145 +1699,163 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_func_verify_k256_sha256( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_create_dm( + fun uniffi_xmtpv3_checksum_method_fficonversation_add_admin( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_create_group( + fun uniffi_xmtpv3_checksum_method_fficonversation_add_members( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_list( + fun uniffi_xmtpv3_checksum_method_fficonversation_add_members_by_inbox_id( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_process_streamed_welcome_message( + fun uniffi_xmtpv3_checksum_method_fficonversation_add_super_admin( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_stream( + fun uniffi_xmtpv3_checksum_method_fficonversation_added_by_inbox_id( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages( + fun uniffi_xmtpv3_checksum_method_fficonversation_admin_list( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_sync( + fun uniffi_xmtpv3_checksum_method_fficonversation_consent_state( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversation_created_at_ns( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversation_find_messages( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversation_group_description( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversation_group_image_url_square( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversation_group_metadata( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversation_group_name( ): Short - fun uniffi_xmtpv3_checksum_method_fficonversations_sync_all_groups( + fun uniffi_xmtpv3_checksum_method_fficonversation_group_permissions( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_add_admin( + fun uniffi_xmtpv3_checksum_method_fficonversation_group_pinned_frame_url( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_add_members( + fun uniffi_xmtpv3_checksum_method_fficonversation_id( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_add_members_by_inbox_id( + fun uniffi_xmtpv3_checksum_method_fficonversation_is_active( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_add_super_admin( + fun uniffi_xmtpv3_checksum_method_fficonversation_is_admin( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_added_by_inbox_id( + fun uniffi_xmtpv3_checksum_method_fficonversation_is_super_admin( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_admin_list( + fun uniffi_xmtpv3_checksum_method_fficonversation_list_members( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_consent_state( + fun uniffi_xmtpv3_checksum_method_fficonversation_process_streamed_conversation_message( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_created_at_ns( + fun uniffi_xmtpv3_checksum_method_fficonversation_publish_messages( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_find_messages( + fun uniffi_xmtpv3_checksum_method_fficonversation_remove_admin( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_group_description( + fun uniffi_xmtpv3_checksum_method_fficonversation_remove_members( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_group_image_url_square( + fun uniffi_xmtpv3_checksum_method_fficonversation_remove_members_by_inbox_id( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_group_metadata( + fun uniffi_xmtpv3_checksum_method_fficonversation_remove_super_admin( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_group_name( + fun uniffi_xmtpv3_checksum_method_fficonversation_send( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_group_permissions( + fun uniffi_xmtpv3_checksum_method_fficonversation_send_optimistic( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_group_pinned_frame_url( + fun uniffi_xmtpv3_checksum_method_fficonversation_stream( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_id( + fun uniffi_xmtpv3_checksum_method_fficonversation_super_admin_list( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_is_active( + fun uniffi_xmtpv3_checksum_method_fficonversation_sync( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_is_admin( + fun uniffi_xmtpv3_checksum_method_fficonversation_update_consent_state( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_is_super_admin( + fun uniffi_xmtpv3_checksum_method_fficonversation_update_group_description( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_list_members( + fun uniffi_xmtpv3_checksum_method_fficonversation_update_group_image_url_square( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_process_streamed_group_message( + fun uniffi_xmtpv3_checksum_method_fficonversation_update_group_name( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_publish_messages( + fun uniffi_xmtpv3_checksum_method_fficonversation_update_group_pinned_frame_url( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_remove_admin( + fun uniffi_xmtpv3_checksum_method_fficonversation_update_permission_policy( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_remove_members( + fun uniffi_xmtpv3_checksum_method_fficonversationmetadata_conversation_type( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_remove_members_by_inbox_id( + fun uniffi_xmtpv3_checksum_method_fficonversationmetadata_creator_inbox_id( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_remove_super_admin( + fun uniffi_xmtpv3_checksum_method_fficonversations_create_dm( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_send( + fun uniffi_xmtpv3_checksum_method_fficonversations_create_group( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_send_optimistic( + fun uniffi_xmtpv3_checksum_method_fficonversations_list( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_stream( + fun uniffi_xmtpv3_checksum_method_fficonversations_list_dms( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_super_admin_list( + fun uniffi_xmtpv3_checksum_method_fficonversations_list_groups( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_sync( + fun uniffi_xmtpv3_checksum_method_fficonversations_process_streamed_welcome_message( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_update_consent_state( + fun uniffi_xmtpv3_checksum_method_fficonversations_stream( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_update_group_description( + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_all_dm_messages( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_update_group_image_url_square( + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_all_group_messages( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_update_group_name( + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_update_group_pinned_frame_url( + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_dms( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroup_update_permission_policy( + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_groups( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroupmetadata_conversation_type( + fun uniffi_xmtpv3_checksum_method_fficonversations_sync( ): Short - fun uniffi_xmtpv3_checksum_method_ffigroupmetadata_creator_inbox_id( + fun uniffi_xmtpv3_checksum_method_fficonversations_sync_all_conversations( ): Short fun uniffi_xmtpv3_checksum_method_ffigrouppermissions_policy_set( @@ -1865,18 +1915,27 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffixmtpclient_add_wallet( ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_addresses_from_inbox_id( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_apply_signature_request( ): Short fun uniffi_xmtpv3_checksum_method_ffixmtpclient_can_message( ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_conversation( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_conversations( ): Short fun uniffi_xmtpv3_checksum_method_ffixmtpclient_db_reconnect( ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_dm_conversation( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_find_inbox_id( ): Short @@ -1886,9 +1945,6 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffixmtpclient_get_latest_inbox_state( ): Short - fun uniffi_xmtpv3_checksum_method_ffixmtpclient_group( - ): Short - fun uniffi_xmtpv3_checksum_method_ffixmtpclient_inbox_id( ): Short @@ -2005,145 +2061,163 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_func_verify_k256_sha256() != 25521.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_create_dm() != 61687.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_add_admin() != 52417.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_create_group() != 62996.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_add_members() != 3260.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list() != 10804.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_add_members_by_inbox_id() != 28069.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_process_streamed_welcome_message() != 15283.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_add_super_admin() != 62984.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream() != 3079.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_added_by_inbox_id() != 12748.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages() != 13204.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_admin_list() != 26668.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync() != 9054.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_consent_state() != 25033.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_created_at_ns() != 17973.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_find_messages() != 58508.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_group_description() != 53570.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_group_image_url_square() != 3200.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_group_metadata() != 21111.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_group_name() != 9344.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync_all_groups() != 3433.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_group_permissions() != 61947.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_add_admin() != 4600.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_group_pinned_frame_url() != 40964.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_add_members() != 27666.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_id() != 5542.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_add_members_by_inbox_id() != 23290.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_is_active() != 49581.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_add_super_admin() != 40681.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_is_admin() != 12325.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_added_by_inbox_id() != 37220.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_is_super_admin() != 25811.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_admin_list() != 51010.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_list_members() != 21260.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_consent_state() != 11630.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_process_streamed_conversation_message() != 1417.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_created_at_ns() != 4894.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_publish_messages() != 15643.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_find_messages() != 14930.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_remove_admin() != 7973.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_description() != 37045.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_remove_members() != 49452.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_image_url_square() != 16754.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_remove_members_by_inbox_id() != 53192.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_metadata() != 13139.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_remove_super_admin() != 46017.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_name() != 61525.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_send() != 7954.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_permissions() != 15980.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_send_optimistic() != 5885.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_pinned_frame_url() != 29388.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_stream() != 52815.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_id() != 36764.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_super_admin_list() != 50610.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_is_active() != 33848.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_sync() != 17206.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_is_admin() != 26672.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_update_consent_state() != 27721.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_is_super_admin() != 61614.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_update_group_description() != 14549.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_list_members() != 3945.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_update_group_image_url_square() != 36900.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_process_streamed_group_message() != 19069.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_update_group_name() != 62600.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_publish_messages() != 52808.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_update_group_pinned_frame_url() != 21997.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_remove_admin() != 57094.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_update_permission_policy() != 3743.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_remove_members() != 24336.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversationmetadata_conversation_type() != 48024.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_remove_members_by_inbox_id() != 45424.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversationmetadata_creator_inbox_id() != 61067.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_remove_super_admin() != 35336.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_create_dm() != 63785.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_send() != 37701.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_create_group() != 7282.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_send_optimistic() != 13872.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list() != 42790.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_stream() != 34669.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list_dms() != 41576.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_super_admin_list() != 5323.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list_groups() != 2386.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_sync() != 24219.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_process_streamed_welcome_message() != 57376.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_update_consent_state() != 48124.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream() != 3079.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_update_group_description() != 34006.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_all_dm_messages() != 37950.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_update_group_image_url_square() != 18878.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_all_group_messages() != 50601.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_update_group_name() != 550.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages() != 13204.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_update_group_pinned_frame_url() != 37434.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_dms() != 4319.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroup_update_permission_policy() != 51936.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_groups() != 10208.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroupmetadata_conversation_type() != 30827.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync() != 9054.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffigroupmetadata_creator_inbox_id() != 26872.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync_all_conversations() != 1140.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffigrouppermissions_policy_set() != 24928.toShort()) { @@ -2203,18 +2277,27 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_add_wallet() != 23786.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_addresses_from_inbox_id() != 29264.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_apply_signature_request() != 32172.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_can_message() != 53502.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_conversation() != 60290.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_conversations() != 47463.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_db_reconnect() != 6707.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_dm_conversation() != 23917.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_find_inbox_id() != 59020.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2224,9 +2307,6 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_get_latest_inbox_state() != 3165.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_group() != 64533.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_inbox_id() != 25128.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2269,7 +2349,7 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffilogger_log() != 56011.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversationcallback_on_conversation() != 42.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversationcallback_on_conversation() != 25316.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffimessagecallback_on_message() != 5286.toShort()) { @@ -2690,31 +2770,96 @@ private class JavaLangRefCleanable( override fun clean() = cleanable.clean() } -public interface FfiConversationsInterface { +public interface FfiConversationInterface { - suspend fun `createDm`(`accountAddress`: kotlin.String): FfiGroup + suspend fun `addAdmin`(`inboxId`: kotlin.String) - suspend fun `createGroup`( - `accountAddresses`: List, - `opts`: FfiCreateGroupOptions, - ): FfiGroup + suspend fun `addMembers`(`accountAddresses`: List) - suspend fun `list`(`opts`: FfiListConversationsOptions): List + suspend fun `addMembersByInboxId`(`inboxIds`: List) - suspend fun `processStreamedWelcomeMessage`(`envelopeBytes`: kotlin.ByteArray): FfiGroup + suspend fun `addSuperAdmin`(`inboxId`: kotlin.String) - suspend fun `stream`(`callback`: FfiConversationCallback): FfiStreamCloser + fun `addedByInboxId`(): kotlin.String - suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser + fun `adminList`(): List + + fun `consentState`(): FfiConsentState + + fun `createdAtNs`(): kotlin.Long + + fun `findMessages`(`opts`: FfiListMessagesOptions): List + + fun `groupDescription`(): kotlin.String + + fun `groupImageUrlSquare`(): kotlin.String + + fun `groupMetadata`(): FfiConversationMetadata + + fun `groupName`(): kotlin.String + + fun `groupPermissions`(): FfiGroupPermissions + + fun `groupPinnedFrameUrl`(): kotlin.String + + fun `id`(): kotlin.ByteArray + + fun `isActive`(): kotlin.Boolean + + fun `isAdmin`(`inboxId`: kotlin.String): kotlin.Boolean + + fun `isSuperAdmin`(`inboxId`: kotlin.String): kotlin.Boolean + + suspend fun `listMembers`(): List + + suspend fun `processStreamedConversationMessage`(`envelopeBytes`: kotlin.ByteArray): FfiMessage + + /** + * Publish all unpublished messages + */ + suspend fun `publishMessages`() + + suspend fun `removeAdmin`(`inboxId`: kotlin.String) + + suspend fun `removeMembers`(`accountAddresses`: List) + + suspend fun `removeMembersByInboxId`(`inboxIds`: List) + + suspend fun `removeSuperAdmin`(`inboxId`: kotlin.String) + + suspend fun `send`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray + + /** + * send a message without immediately publishing to the delivery service. + */ + fun `sendOptimistic`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray + + suspend fun `stream`(`messageCallback`: FfiMessageCallback): FfiStreamCloser + + fun `superAdminList`(): List suspend fun `sync`() - suspend fun `syncAllGroups`(): kotlin.UInt + fun `updateConsentState`(`state`: FfiConsentState) + + suspend fun `updateGroupDescription`(`groupDescription`: kotlin.String) + + suspend fun `updateGroupImageUrlSquare`(`groupImageUrlSquare`: kotlin.String) + + suspend fun `updateGroupName`(`groupName`: kotlin.String) + + suspend fun `updateGroupPinnedFrameUrl`(`pinnedFrameUrl`: kotlin.String) + + suspend fun `updatePermissionPolicy`( + `permissionUpdateType`: FfiPermissionUpdateType, + `permissionPolicyOption`: FfiPermissionPolicy, + `metadataField`: FfiMetadataField?, + ) companion object } -open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterface { +open class FfiConversation : Disposable, AutoCloseable, FfiConversationInterface { constructor(pointer: Pointer) { this.pointer = pointer @@ -2783,7 +2928,7 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa override fun run() { pointer?.let { ptr -> uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_fficonversations(ptr, status) + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_fficonversation(ptr, status) } } } @@ -2791,38 +2936,39 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa fun uniffiClonePointer(): Pointer { return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_fficonversations(pointer!!, status) + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_fficonversation(pointer!!, status) } } @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `createDm`(`accountAddress`: kotlin.String): FfiGroup { + override suspend fun `addAdmin`(`inboxId`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_create_dm( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_add_admin( thisPtr, - FfiConverterString.lower(`accountAddress`), + FfiConverterString.lower(`inboxId`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, // lift function - { FfiConverterTypeFfiGroup.lift(it) }, - // Error FFI converter + { Unit }, + + // Error FFI converter GenericException.ErrorHandler, ) } @@ -2830,34 +2976,31 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `createGroup`( - `accountAddresses`: List, - `opts`: FfiCreateGroupOptions, - ): FfiGroup { + override suspend fun `addMembers`(`accountAddresses`: List) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_create_group( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_add_members( thisPtr, FfiConverterSequenceString.lower(`accountAddresses`), - FfiConverterTypeFfiCreateGroupOptions.lower(`opts`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, // lift function - { FfiConverterTypeFfiGroup.lift(it) }, + { Unit }, + // Error FFI converter GenericException.ErrorHandler, ) @@ -2866,30 +3009,31 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `list`(`opts`: FfiListConversationsOptions): List { + override suspend fun `addMembersByInboxId`(`inboxIds`: List) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_list( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_add_members_by_inbox_id( thisPtr, - FfiConverterTypeFfiListConversationsOptions.lower(`opts`), + FfiConverterSequenceString.lower(`inboxIds`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, // lift function - { FfiConverterSequenceTypeFfiGroup.lift(it) }, + { Unit }, + // Error FFI converter GenericException.ErrorHandler, ) @@ -2898,104 +3042,318 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `processStreamedWelcomeMessage`(`envelopeBytes`: kotlin.ByteArray): FfiGroup { + override suspend fun `addSuperAdmin`(`inboxId`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_process_streamed_welcome_message( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_add_super_admin( thisPtr, - FfiConverterByteArray.lower(`envelopeBytes`), + FfiConverterString.lower(`inboxId`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, // lift function - { FfiConverterTypeFfiGroup.lift(it) }, + { Unit }, + // Error FFI converter GenericException.ErrorHandler, ) } + @Throws(GenericException::class) + override fun `addedByInboxId`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_added_by_inbox_id( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `adminList`(): List { + return FfiConverterSequenceString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_admin_list( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `consentState`(): FfiConsentState { + return FfiConverterTypeFfiConsentState.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_consent_state( + it, _status + ) + } + } + ) + } + + + override fun `createdAtNs`(): kotlin.Long { + return FfiConverterLong.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_created_at_ns( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `findMessages`(`opts`: FfiListMessagesOptions): List { + return FfiConverterSequenceTypeFfiMessage.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_find_messages( + it, FfiConverterTypeFfiListMessagesOptions.lower(`opts`), _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `groupDescription`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_group_description( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `groupImageUrlSquare`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_group_image_url_square( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `groupMetadata`(): FfiConversationMetadata { + return FfiConverterTypeFfiConversationMetadata.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_group_metadata( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `groupName`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_group_name( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `groupPermissions`(): FfiGroupPermissions { + return FfiConverterTypeFfiGroupPermissions.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_group_permissions( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `groupPinnedFrameUrl`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_group_pinned_frame_url( + it, _status + ) + } + } + ) + } + + + override fun `id`(): kotlin.ByteArray { + return FfiConverterByteArray.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_id( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `isActive`(): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_is_active( + it, _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `isAdmin`(`inboxId`: kotlin.String): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_is_admin( + it, FfiConverterString.lower(`inboxId`), _status + ) + } + } + ) + } + + + @Throws(GenericException::class) + override fun `isSuperAdmin`(`inboxId`: kotlin.String): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_is_super_admin( + it, FfiConverterString.lower(`inboxId`), _status + ) + } + } + ) + } + + + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `stream`(`callback`: FfiConversationCallback): FfiStreamCloser { + override suspend fun `listMembers`(): List { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_list_members( thisPtr, - FfiConverterTypeFfiConversationCallback.lower(`callback`), - ) + + ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, // lift function - { FfiConverterTypeFfiStreamCloser.lift(it) }, + { FfiConverterSequenceTypeFfiConversationMember.lift(it) }, // Error FFI converter - UniffiNullRustCallStatusErrorHandler, + GenericException.ErrorHandler, ) } + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser { + override suspend fun `processStreamedConversationMessage`(`envelopeBytes`: kotlin.ByteArray): FfiMessage { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_all_messages( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_process_streamed_conversation_message( thisPtr, - FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), + FfiConverterByteArray.lower(`envelopeBytes`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, // lift function - { FfiConverterTypeFfiStreamCloser.lift(it) }, + { FfiConverterTypeFfiMessage.lift(it) }, // Error FFI converter - UniffiNullRustCallStatusErrorHandler, + GenericException.ErrorHandler, ) } + /** + * Publish all unpublished messages + */ @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `sync`() { + override suspend fun `publishMessages`() { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_sync( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_publish_messages( thisPtr, ) @@ -3025,343 +3383,282 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `syncAllGroups`(): kotlin.UInt { + override suspend fun `removeAdmin`(`inboxId`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_sync_all_groups( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_remove_admin( thisPtr, + FfiConverterString.lower(`inboxId`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, - ) + // Error FFI converter + GenericException.ErrorHandler, + ) + } + + + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `removeMembers`(`accountAddresses`: List) { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_remove_members( + thisPtr, + FfiConverterSequenceString.lower(`accountAddresses`), + ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_u32( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_u32( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_u32(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, // lift function - { FfiConverterUInt.lift(it) }, + { Unit }, + // Error FFI converter GenericException.ErrorHandler, ) } - companion object + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `removeMembersByInboxId`(`inboxIds`: List) { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_remove_members_by_inbox_id( + thisPtr, + FfiConverterSequenceString.lower(`inboxIds`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, -} + // Error FFI converter + GenericException.ErrorHandler, + ) + } -public object FfiConverterTypeFfiConversations : FfiConverter { - override fun lower(value: FfiConversations): Pointer { - return value.uniffiClonePointer() + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `removeSuperAdmin`(`inboxId`: kotlin.String) { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_remove_super_admin( + thisPtr, + FfiConverterString.lower(`inboxId`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + GenericException.ErrorHandler, + ) } - override fun lift(value: Pointer): FfiConversations { - return FfiConversations(value) + + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `send`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_send( + thisPtr, + FfiConverterByteArray.lower(`contentBytes`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterByteArray.lift(it) }, + // Error FFI converter + GenericException.ErrorHandler, + ) } - override fun read(buf: ByteBuffer): FfiConversations { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) + + /** + * send a message without immediately publishing to the delivery service. + */ + @Throws(GenericException::class) + override fun `sendOptimistic`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray { + return FfiConverterByteArray.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_send_optimistic( + it, FfiConverterByteArray.lower(`contentBytes`), _status + ) + } + } + ) } - override fun allocationSize(value: FfiConversations) = 8UL - override fun write(value: FfiConversations, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `stream`(`messageCallback`: FfiMessageCallback): FfiStreamCloser { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_stream( + thisPtr, + FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + // lift function + { FfiConverterTypeFfiStreamCloser.lift(it) }, + // Error FFI converter + UniffiNullRustCallStatusErrorHandler, + ) } -} -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - -public interface FfiGroupInterface { - - suspend fun `addAdmin`(`inboxId`: kotlin.String) - - suspend fun `addMembers`(`accountAddresses`: List) - - suspend fun `addMembersByInboxId`(`inboxIds`: List) - - suspend fun `addSuperAdmin`(`inboxId`: kotlin.String) - - fun `addedByInboxId`(): kotlin.String - - fun `adminList`(): List - - fun `consentState`(): FfiConsentState - - fun `createdAtNs`(): kotlin.Long - - fun `findMessages`(`opts`: FfiListMessagesOptions): List - - fun `groupDescription`(): kotlin.String - - fun `groupImageUrlSquare`(): kotlin.String - - fun `groupMetadata`(): FfiGroupMetadata - - fun `groupName`(): kotlin.String - - fun `groupPermissions`(): FfiGroupPermissions - - fun `groupPinnedFrameUrl`(): kotlin.String - - fun `id`(): kotlin.ByteArray - - fun `isActive`(): kotlin.Boolean - - fun `isAdmin`(`inboxId`: kotlin.String): kotlin.Boolean - - fun `isSuperAdmin`(`inboxId`: kotlin.String): kotlin.Boolean - - suspend fun `listMembers`(): List - - suspend fun `processStreamedGroupMessage`(`envelopeBytes`: kotlin.ByteArray): FfiMessage - - /** - * Publish all unpublished messages - */ - suspend fun `publishMessages`() - - suspend fun `removeAdmin`(`inboxId`: kotlin.String) - - suspend fun `removeMembers`(`accountAddresses`: List) - - suspend fun `removeMembersByInboxId`(`inboxIds`: List) - - suspend fun `removeSuperAdmin`(`inboxId`: kotlin.String) - - suspend fun `send`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray - - /** - * send a message without immediately publishing to the delivery service. - */ - fun `sendOptimistic`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray - - suspend fun `stream`(`messageCallback`: FfiMessageCallback): FfiStreamCloser - - fun `superAdminList`(): List - - suspend fun `sync`() - - fun `updateConsentState`(`state`: FfiConsentState) - - suspend fun `updateGroupDescription`(`groupDescription`: kotlin.String) - - suspend fun `updateGroupImageUrlSquare`(`groupImageUrlSquare`: kotlin.String) - - suspend fun `updateGroupName`(`groupName`: kotlin.String) - - suspend fun `updateGroupPinnedFrameUrl`(`pinnedFrameUrl`: kotlin.String) - - suspend fun `updatePermissionPolicy`( - `permissionUpdateType`: FfiPermissionUpdateType, - `permissionPolicyOption`: FfiPermissionPolicy, - `metadataField`: FfiMetadataField?, - ) - - companion object -} - -open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + @Throws(GenericException::class) + override fun `superAdminList`(): List { + return FfiConverterSequenceString.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_super_admin_list( + it, _status + ) + } + } + ) } - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `sync`() { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_sync( + thisPtr, - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, - @Synchronized - override fun close() { - this.destroy() + // Error FFI converter + GenericException.ErrorHandler, + ) } - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (!this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffigroup(ptr, status) - } + @Throws(GenericException::class) + override fun `updateConsentState`(`state`: FfiConsentState) = + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_update_consent_state( + it, FfiConverterTypeFfiConsentState.lower(`state`), _status + ) } } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffigroup(pointer!!, status) - } - } @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addAdmin`(`inboxId`: kotlin.String) { + override suspend fun `updateGroupDescription`(`groupDescription`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_add_admin( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_update_group_description( thisPtr, - FfiConverterString.lower(`inboxId`), + FfiConverterString.lower(`groupDescription`), ) }, { future, callback, continuation -> @@ -3389,12 +3686,12 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addMembers`(`accountAddresses`: List) { + override suspend fun `updateGroupImageUrlSquare`(`groupImageUrlSquare`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_add_members( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_update_group_image_url_square( thisPtr, - FfiConverterSequenceString.lower(`accountAddresses`), + FfiConverterString.lower(`groupImageUrlSquare`), ) }, { future, callback, continuation -> @@ -3422,12 +3719,12 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addMembersByInboxId`(`inboxIds`: List) { + override suspend fun `updateGroupName`(`groupName`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_add_members_by_inbox_id( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_update_group_name( thisPtr, - FfiConverterSequenceString.lower(`inboxIds`), + FfiConverterString.lower(`groupName`), ) }, { future, callback, continuation -> @@ -3455,12 +3752,12 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addSuperAdmin`(`inboxId`: kotlin.String) { + override suspend fun `updateGroupPinnedFrameUrl`(`pinnedFrameUrl`: kotlin.String) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_add_super_admin( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_update_group_pinned_frame_url( thisPtr, - FfiConverterString.lower(`inboxId`), + FfiConverterString.lower(`pinnedFrameUrl`), ) }, { future, callback, continuation -> @@ -3487,150 +3784,267 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) - override fun `addedByInboxId`(): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_added_by_inbox_id( - it, _status - ) - } - } + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `updatePermissionPolicy`( + `permissionUpdateType`: FfiPermissionUpdateType, + `permissionPolicyOption`: FfiPermissionPolicy, + `metadataField`: FfiMetadataField?, + ) { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_update_permission_policy( + thisPtr, + FfiConverterTypeFfiPermissionUpdateType.lower(`permissionUpdateType`), + FfiConverterTypeFfiPermissionPolicy.lower(`permissionPolicyOption`), + FfiConverterOptionalTypeFfiMetadataField.lower(`metadataField`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + GenericException.ErrorHandler, ) } - @Throws(GenericException::class) - override fun `adminList`(): List { - return FfiConverterSequenceString.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_admin_list( - it, _status - ) - } - } - ) - } + companion object +} - @Throws(GenericException::class) - override fun `consentState`(): FfiConsentState { - return FfiConverterTypeFfiConsentState.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_consent_state( - it, _status - ) - } - } - ) +public object FfiConverterTypeFfiConversation : FfiConverter { + + override fun lower(value: FfiConversation): Pointer { + return value.uniffiClonePointer() } + override fun lift(value: Pointer): FfiConversation { + return FfiConversation(value) + } - override fun `createdAtNs`(): kotlin.Long { - return FfiConverterLong.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_created_at_ns( - it, _status - ) - } - } - ) + override fun read(buf: ByteBuffer): FfiConversation { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) } + override fun allocationSize(value: FfiConversation) = 8UL - @Throws(GenericException::class) - override fun `findMessages`(`opts`: FfiListMessagesOptions): List { - return FfiConverterSequenceTypeFfiMessage.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_find_messages( - it, FfiConverterTypeFfiListMessagesOptions.lower(`opts`), _status - ) - } - } - ) + override fun write(value: FfiConversation, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) } +} - @Throws(GenericException::class) - override fun `groupDescription`(): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_description( - it, _status - ) - } - } - ) - } +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// - @Throws(GenericException::class) - override fun `groupImageUrlSquare`(): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_image_url_square( - it, _status - ) - } - } - ) +public interface FfiConversationMetadataInterface { + + fun `conversationType`(): kotlin.String + + fun `creatorInboxId`(): kotlin.String + + companion object +} + +open class FfiConversationMetadata : Disposable, AutoCloseable, FfiConversationMetadataInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) } + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } - @Throws(GenericException::class) - override fun `groupMetadata`(): FfiGroupMetadata { - return FfiConverterTypeFfiGroupMetadata.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_metadata( - it, _status - ) - } + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() } - ) + } } + @Synchronized + override fun close() { + this.destroy() + } - @Throws(GenericException::class) - override fun `groupName`(): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_name( - it, _status - ) - } + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") } - ) + } while (!this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } } - - @Throws(GenericException::class) - override fun `groupPermissions`(): FfiGroupPermissions { - return FfiConverterTypeFfiGroupPermissions.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_permissions( - it, _status - ) + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_fficonversationmetadata(ptr, status) } } - ) + } } + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_fficonversationmetadata(pointer!!, status) + } + } - @Throws(GenericException::class) - override fun `groupPinnedFrameUrl`(): kotlin.String { + override fun `conversationType`(): kotlin.String { return FfiConverterString.lift( callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_pinned_frame_url( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversationmetadata_conversation_type( it, _status ) } @@ -3639,11 +4053,11 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { } - override fun `id`(): kotlin.ByteArray { - return FfiConverterByteArray.lift( + override fun `creatorInboxId`(): kotlin.String { + return FfiConverterString.lift( callWithPointer { uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_id( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversationmetadata_creator_inbox_id( it, _status ) } @@ -3652,175 +4066,279 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { } - @Throws(GenericException::class) - override fun `isActive`(): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_is_active( - it, _status - ) - } - } - ) + companion object + +} + +public object FfiConverterTypeFfiConversationMetadata : + FfiConverter { + + override fun lower(value: FfiConversationMetadata): Pointer { + return value.uniffiClonePointer() } + override fun lift(value: Pointer): FfiConversationMetadata { + return FfiConversationMetadata(value) + } - @Throws(GenericException::class) - override fun `isAdmin`(`inboxId`: kotlin.String): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_is_admin( - it, FfiConverterString.lower(`inboxId`), _status - ) - } - } - ) + override fun read(buf: ByteBuffer): FfiConversationMetadata { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) } + override fun allocationSize(value: FfiConversationMetadata) = 8UL - @Throws(GenericException::class) - override fun `isSuperAdmin`(`inboxId`: kotlin.String): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_is_super_admin( - it, FfiConverterString.lower(`inboxId`), _status - ) - } - } - ) + override fun write(value: FfiConversationMetadata, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) } +} - @Throws(GenericException::class) - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `listMembers`(): List { - return uniffiRustCallAsync( - callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_list_members( - thisPtr, +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// - ) - }, - { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( - future, - callback, - continuation - ) - }, - { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( - future, - continuation - ) - }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterSequenceTypeFfiGroupMember.lift(it) }, - // Error FFI converter - GenericException.ErrorHandler, - ) - } +public interface FfiConversationsInterface { - @Throws(GenericException::class) - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `processStreamedGroupMessage`(`envelopeBytes`: kotlin.ByteArray): FfiMessage { - return uniffiRustCallAsync( - callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_process_streamed_group_message( - thisPtr, - FfiConverterByteArray.lower(`envelopeBytes`), - ) - }, - { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( - future, - callback, - continuation - ) - }, - { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( - future, - continuation - ) - }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, - // lift function - { FfiConverterTypeFfiMessage.lift(it) }, - // Error FFI converter - GenericException.ErrorHandler, - ) - } + suspend fun `createDm`(`accountAddress`: kotlin.String): FfiConversation + + suspend fun `createGroup`( + `accountAddresses`: List, + `opts`: FfiCreateGroupOptions, + ): FfiConversation + + suspend fun `list`(`opts`: FfiListConversationsOptions): List + + suspend fun `listDms`(`opts`: FfiListConversationsOptions): List + + suspend fun `listGroups`(`opts`: FfiListConversationsOptions): List + + suspend fun `processStreamedWelcomeMessage`(`envelopeBytes`: kotlin.ByteArray): FfiConversation + + suspend fun `stream`(`callback`: FfiConversationCallback): FfiStreamCloser + + suspend fun `streamAllDmMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser + + suspend fun `streamAllGroupMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser + + suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser + + suspend fun `streamDms`(`callback`: FfiConversationCallback): FfiStreamCloser + + suspend fun `streamGroups`(`callback`: FfiConversationCallback): FfiStreamCloser + + suspend fun `sync`() + + suspend fun `syncAllConversations`(): kotlin.UInt + + companion object +} + +open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterface { + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } /** - * Publish all unpublished messages + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. */ - @Throws(GenericException::class) - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `publishMessages`() { - return uniffiRustCallAsync( - callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_publish_messages( - thisPtr, + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } - ) - }, - { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( - future, - callback, - continuation - ) - }, - { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( - future, - continuation - ) - }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, - // lift function - { Unit }, + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable - // Error FFI converter - GenericException.ErrorHandler, - ) + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_fficonversations(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_fficonversations(pointer!!, status) + } } @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `removeAdmin`(`inboxId`: kotlin.String) { + override suspend fun `createDm`(`accountAddress`: kotlin.String): FfiConversation { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_remove_admin( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_create_dm( thisPtr, - FfiConverterString.lower(`inboxId`), + FfiConverterString.lower(`accountAddress`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiConversation.lift(it) }, // Error FFI converter GenericException.ErrorHandler, ) @@ -3829,31 +4347,34 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `removeMembers`(`accountAddresses`: List) { + override suspend fun `createGroup`( + `accountAddresses`: List, + `opts`: FfiCreateGroupOptions, + ): FfiConversation { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_remove_members( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_create_group( thisPtr, FfiConverterSequenceString.lower(`accountAddresses`), + FfiConverterTypeFfiCreateGroupOptions.lower(`opts`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiConversation.lift(it) }, // Error FFI converter GenericException.ErrorHandler, ) @@ -3862,31 +4383,30 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `removeMembersByInboxId`(`inboxIds`: List) { + override suspend fun `list`(`opts`: FfiListConversationsOptions): List { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_remove_members_by_inbox_id( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_list( thisPtr, - FfiConverterSequenceString.lower(`inboxIds`), + FfiConverterTypeFfiListConversationsOptions.lower(`opts`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, // lift function - { Unit }, - + { FfiConverterSequenceTypeFfiConversation.lift(it) }, // Error FFI converter GenericException.ErrorHandler, ) @@ -3895,31 +4415,30 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `removeSuperAdmin`(`inboxId`: kotlin.String) { + override suspend fun `listDms`(`opts`: FfiListConversationsOptions): List { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_remove_super_admin( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_list_dms( thisPtr, - FfiConverterString.lower(`inboxId`), + FfiConverterTypeFfiListConversationsOptions.lower(`opts`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, // lift function - { Unit }, - + { FfiConverterSequenceTypeFfiConversation.lift(it) }, // Error FFI converter GenericException.ErrorHandler, ) @@ -3928,12 +4447,12 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `send`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray { + override suspend fun `listGroups`(`opts`: FfiListConversationsOptions): List { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_send( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_list_groups( thisPtr, - FfiConverterByteArray.lower(`contentBytes`), + FfiConverterTypeFfiListConversationsOptions.lower(`opts`), ) }, { future, callback, continuation -> @@ -3951,37 +4470,21 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { }, { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, // lift function - { FfiConverterByteArray.lift(it) }, + { FfiConverterSequenceTypeFfiConversation.lift(it) }, // Error FFI converter GenericException.ErrorHandler, ) } - /** - * send a message without immediately publishing to the delivery service. - */ @Throws(GenericException::class) - override fun `sendOptimistic`(`contentBytes`: kotlin.ByteArray): kotlin.ByteArray { - return FfiConverterByteArray.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_send_optimistic( - it, FfiConverterByteArray.lower(`contentBytes`), _status - ) - } - } - ) - } - - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `stream`(`messageCallback`: FfiMessageCallback): FfiStreamCloser { + override suspend fun `processStreamedWelcomeMessage`(`envelopeBytes`: kotlin.ByteArray): FfiConversation { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_stream( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_process_streamed_welcome_message( thisPtr, - FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), + FfiConverterByteArray.lower(`envelopeBytes`), ) }, { future, callback, continuation -> @@ -3999,482 +4502,260 @@ open class FfiGroup : Disposable, AutoCloseable, FfiGroupInterface { }, { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { FfiConverterTypeFfiStreamCloser.lift(it) }, + { FfiConverterTypeFfiConversation.lift(it) }, // Error FFI converter - UniffiNullRustCallStatusErrorHandler, - ) - } - - - @Throws(GenericException::class) - override fun `superAdminList`(): List { - return FfiConverterSequenceString.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_super_admin_list( - it, _status - ) - } - } + GenericException.ErrorHandler, ) } - @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `sync`() { + override suspend fun `stream`(`callback`: FfiConversationCallback): FfiStreamCloser { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_sync( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream( thisPtr, - - ) + FfiConverterTypeFfiConversationCallback.lower(`callback`), + ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiStreamCloser.lift(it) }, // Error FFI converter - GenericException.ErrorHandler, + UniffiNullRustCallStatusErrorHandler, ) } - @Throws(GenericException::class) - override fun `updateConsentState`(`state`: FfiConsentState) = - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_update_consent_state( - it, FfiConverterTypeFfiConsentState.lower(`state`), _status - ) - } - } - - - @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `updateGroupDescription`(`groupDescription`: kotlin.String) { + override suspend fun `streamAllDmMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_update_group_description( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_all_dm_messages( thisPtr, - FfiConverterString.lower(`groupDescription`), + FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiStreamCloser.lift(it) }, // Error FFI converter - GenericException.ErrorHandler, + UniffiNullRustCallStatusErrorHandler, ) } - @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `updateGroupImageUrlSquare`(`groupImageUrlSquare`: kotlin.String) { + override suspend fun `streamAllGroupMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_update_group_image_url_square( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_all_group_messages( thisPtr, - FfiConverterString.lower(`groupImageUrlSquare`), + FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiStreamCloser.lift(it) }, // Error FFI converter - GenericException.ErrorHandler, + UniffiNullRustCallStatusErrorHandler, ) } - @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `updateGroupName`(`groupName`: kotlin.String) { + override suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_update_group_name( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_all_messages( thisPtr, - FfiConverterString.lower(`groupName`), + FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiStreamCloser.lift(it) }, // Error FFI converter - GenericException.ErrorHandler, + UniffiNullRustCallStatusErrorHandler, ) } - @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `updateGroupPinnedFrameUrl`(`pinnedFrameUrl`: kotlin.String) { + override suspend fun `streamDms`(`callback`: FfiConversationCallback): FfiStreamCloser { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_update_group_pinned_frame_url( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_dms( thisPtr, - FfiConverterString.lower(`pinnedFrameUrl`), + FfiConverterTypeFfiConversationCallback.lower(`callback`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiStreamCloser.lift(it) }, // Error FFI converter - GenericException.ErrorHandler, + UniffiNullRustCallStatusErrorHandler, ) } - @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `updatePermissionPolicy`( - `permissionUpdateType`: FfiPermissionUpdateType, - `permissionPolicyOption`: FfiPermissionPolicy, - `metadataField`: FfiMetadataField?, - ) { + override suspend fun `streamGroups`(`callback`: FfiConversationCallback): FfiStreamCloser { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_update_permission_policy( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_groups( thisPtr, - FfiConverterTypeFfiPermissionUpdateType.lower(`permissionUpdateType`), - FfiConverterTypeFfiPermissionPolicy.lower(`permissionPolicyOption`), - FfiConverterOptionalTypeFfiMetadataField.lower(`metadataField`), + FfiConverterTypeFfiConversationCallback.lower(`callback`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiStreamCloser.lift(it) }, // Error FFI converter - GenericException.ErrorHandler, - ) - } - - - companion object - -} - -public object FfiConverterTypeFfiGroup : FfiConverter { - - override fun lower(value: FfiGroup): Pointer { - return value.uniffiClonePointer() - } - - override fun lift(value: Pointer): FfiGroup { - return FfiGroup(value) - } - - override fun read(buf: ByteBuffer): FfiGroup { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: FfiGroup) = 8UL - - override fun write(value: FfiGroup, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - -public interface FfiGroupMetadataInterface { - - fun `conversationType`(): kotlin.String - - fun `creatorInboxId`(): kotlin.String - - companion object -} - -open class FfiGroupMetadata : Disposable, AutoCloseable, FfiGroupMetadataInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (!this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffigroupmetadata(ptr, status) - } - } - } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffigroupmetadata(pointer!!, status) - } + UniffiNullRustCallStatusErrorHandler, + ) } - override fun `conversationType`(): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroupmetadata_conversation_type( - it, _status + + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `sync`() { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_sync( + thisPtr, + ) - } - } + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + GenericException.ErrorHandler, ) } - override fun `creatorInboxId`(): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroupmetadata_creator_inbox_id( - it, _status + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `syncAllConversations`(): kotlin.UInt { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_sync_all_conversations( + thisPtr, + ) - } - } + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_u32( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_u32( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_u32(future) }, + // lift function + { FfiConverterUInt.lift(it) }, + // Error FFI converter + GenericException.ErrorHandler, ) } @@ -4483,25 +4764,25 @@ open class FfiGroupMetadata : Disposable, AutoCloseable, FfiGroupMetadataInterfa } -public object FfiConverterTypeFfiGroupMetadata : FfiConverter { +public object FfiConverterTypeFfiConversations : FfiConverter { - override fun lower(value: FfiGroupMetadata): Pointer { + override fun lower(value: FfiConversations): Pointer { return value.uniffiClonePointer() } - override fun lift(value: Pointer): FfiGroupMetadata { - return FfiGroupMetadata(value) + override fun lift(value: Pointer): FfiConversations { + return FfiConversations(value) } - override fun read(buf: ByteBuffer): FfiGroupMetadata { + override fun read(buf: ByteBuffer): FfiConversations { // The Rust code always writes pointers as 8 bytes, and will // fail to compile if they don't fit. return lift(Pointer(buf.getLong())) } - override fun allocationSize(value: FfiGroupMetadata) = 8UL + override fun allocationSize(value: FfiConversations) = 8UL - override fun write(value: FfiGroupMetadata, buf: ByteBuffer) { + override fun write(value: FfiConversations, buf: ByteBuffer) { // The Rust code always expects pointers written as 8 bytes, // and will fail to compile if they don't fit. buf.putLong(Pointer.nativeValue(lower(value))) @@ -6258,14 +6539,29 @@ public interface FfiXmtpClientInterface { `newWalletAddress`: kotlin.String, ): FfiSignatureRequest + /** + * * Get the inbox state for each `inbox_id`. + * * + * * If `refresh_from_network` is true, the client will go to the network first to refresh the state. + * * Otherwise, the state will be read from the local database. + */ + suspend fun `addressesFromInboxId`( + `refreshFromNetwork`: kotlin.Boolean, + `inboxIds`: List, + ): List + suspend fun `applySignatureRequest`(`signatureRequest`: FfiSignatureRequest) suspend fun `canMessage`(`accountAddresses`: List): Map + fun `conversation`(`conversationId`: kotlin.ByteArray): FfiConversation + fun `conversations`(): FfiConversations suspend fun `dbReconnect`() + fun `dmConversation`(`targetInboxId`: kotlin.String): FfiConversation + suspend fun `findInboxId`(`address`: kotlin.String): kotlin.String? suspend fun `getConsentState`( @@ -6275,8 +6571,6 @@ public interface FfiXmtpClientInterface { suspend fun `getLatestInboxState`(`inboxId`: kotlin.String): FfiInboxState - fun `group`(`groupId`: kotlin.ByteArray): FfiGroup - fun `inboxId`(): kotlin.String /** @@ -6435,6 +6729,48 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } + /** + * * Get the inbox state for each `inbox_id`. + * * + * * If `refresh_from_network` is true, the client will go to the network first to refresh the state. + * * Otherwise, the state will be read from the local database. + */ + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `addressesFromInboxId`( + `refreshFromNetwork`: kotlin.Boolean, + `inboxIds`: List, + ): List { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_addresses_from_inbox_id( + thisPtr, + FfiConverterBoolean.lower(`refreshFromNetwork`), + FfiConverterSequenceString.lower(`inboxIds`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterSequenceTypeFfiInboxState.lift(it) }, + // Error FFI converter + GenericException.ErrorHandler, + ) + } + + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun `applySignatureRequest`(`signatureRequest`: FfiSignatureRequest) { @@ -6499,6 +6835,21 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { ) } + + @Throws(GenericException::class) + override fun `conversation`(`conversationId`: kotlin.ByteArray): FfiConversation { + return FfiConverterTypeFfiConversation.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_conversation( + it, FfiConverterByteArray.lower(`conversationId`), _status + ) + } + } + ) + } + + override fun `conversations`(): FfiConversations { return FfiConverterTypeFfiConversations.lift( callWithPointer { @@ -6545,6 +6896,20 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } + @Throws(GenericException::class) + override fun `dmConversation`(`targetInboxId`: kotlin.String): FfiConversation { + return FfiConverterTypeFfiConversation.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_dm_conversation( + it, FfiConverterString.lower(`targetInboxId`), _status + ) + } + } + ) + } + + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun `findInboxId`(`address`: kotlin.String): kotlin.String? { @@ -6644,21 +7009,6 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { ) } - - @Throws(GenericException::class) - override fun `group`(`groupId`: kotlin.ByteArray): FfiGroup { - return FfiConverterTypeFfiGroup.lift( - callWithPointer { - uniffiRustCallWithError(GenericException) { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_group( - it, FfiConverterByteArray.lower(`groupId`), _status - ) - } - } - ) - } - - override fun `inboxId`(): kotlin.String { return FfiConverterString.lift( callWithPointer { @@ -6990,6 +7340,47 @@ public object FfiConverterTypeFfiConsent : FfiConverterRustBuffer { } +data class FfiConversationMember( + var `inboxId`: kotlin.String, + var `accountAddresses`: List, + var `installationIds`: List, + var `permissionLevel`: FfiPermissionLevel, + var `consentState`: FfiConsentState, +) { + + companion object +} + +public object FfiConverterTypeFfiConversationMember : + FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiConversationMember { + return FfiConversationMember( + FfiConverterString.read(buf), + FfiConverterSequenceString.read(buf), + FfiConverterSequenceByteArray.read(buf), + FfiConverterTypeFfiPermissionLevel.read(buf), + FfiConverterTypeFfiConsentState.read(buf), + ) + } + + override fun allocationSize(value: FfiConversationMember) = ( + FfiConverterString.allocationSize(value.`inboxId`) + + FfiConverterSequenceString.allocationSize(value.`accountAddresses`) + + FfiConverterSequenceByteArray.allocationSize(value.`installationIds`) + + FfiConverterTypeFfiPermissionLevel.allocationSize(value.`permissionLevel`) + + FfiConverterTypeFfiConsentState.allocationSize(value.`consentState`) + ) + + override fun write(value: FfiConversationMember, buf: ByteBuffer) { + FfiConverterString.write(value.`inboxId`, buf) + FfiConverterSequenceString.write(value.`accountAddresses`, buf) + FfiConverterSequenceByteArray.write(value.`installationIds`, buf) + FfiConverterTypeFfiPermissionLevel.write(value.`permissionLevel`, buf) + FfiConverterTypeFfiConsentState.write(value.`consentState`, buf) + } +} + + data class FfiCreateGroupOptions( var `permissions`: FfiGroupPermissionsOptions?, var `groupName`: kotlin.String?, @@ -7095,46 +7486,6 @@ public object FfiConverterTypeFfiEnvelope : FfiConverterRustBuffer } -data class FfiGroupMember( - var `inboxId`: kotlin.String, - var `accountAddresses`: List, - var `installationIds`: List, - var `permissionLevel`: FfiPermissionLevel, - var `consentState`: FfiConsentState, -) { - - companion object -} - -public object FfiConverterTypeFfiGroupMember : FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): FfiGroupMember { - return FfiGroupMember( - FfiConverterString.read(buf), - FfiConverterSequenceString.read(buf), - FfiConverterSequenceByteArray.read(buf), - FfiConverterTypeFfiPermissionLevel.read(buf), - FfiConverterTypeFfiConsentState.read(buf), - ) - } - - override fun allocationSize(value: FfiGroupMember) = ( - FfiConverterString.allocationSize(value.`inboxId`) + - FfiConverterSequenceString.allocationSize(value.`accountAddresses`) + - FfiConverterSequenceByteArray.allocationSize(value.`installationIds`) + - FfiConverterTypeFfiPermissionLevel.allocationSize(value.`permissionLevel`) + - FfiConverterTypeFfiConsentState.allocationSize(value.`consentState`) - ) - - override fun write(value: FfiGroupMember, buf: ByteBuffer) { - FfiConverterString.write(value.`inboxId`, buf) - FfiConverterSequenceString.write(value.`accountAddresses`, buf) - FfiConverterSequenceByteArray.write(value.`installationIds`, buf) - FfiConverterTypeFfiPermissionLevel.write(value.`permissionLevel`, buf) - FfiConverterTypeFfiConsentState.write(value.`consentState`, buf) - } -} - - data class FfiInboxState( var `inboxId`: kotlin.String, var `recoveryAddress`: kotlin.String, @@ -7275,7 +7626,7 @@ data class FfiMessage( var `convoId`: kotlin.ByteArray, var `senderInboxId`: kotlin.String, var `content`: kotlin.ByteArray, - var `kind`: FfiGroupMessageKind, + var `kind`: FfiConversationMessageKind, var `deliveryStatus`: FfiDeliveryStatus, ) { @@ -7290,7 +7641,7 @@ public object FfiConverterTypeFfiMessage : FfiConverterRustBuffer { FfiConverterByteArray.read(buf), FfiConverterString.read(buf), FfiConverterByteArray.read(buf), - FfiConverterTypeFfiGroupMessageKind.read(buf), + FfiConverterTypeFfiConversationMessageKind.read(buf), FfiConverterTypeFfiDeliveryStatus.read(buf), ) } @@ -7301,7 +7652,7 @@ public object FfiConverterTypeFfiMessage : FfiConverterRustBuffer { FfiConverterByteArray.allocationSize(value.`convoId`) + FfiConverterString.allocationSize(value.`senderInboxId`) + FfiConverterByteArray.allocationSize(value.`content`) + - FfiConverterTypeFfiGroupMessageKind.allocationSize(value.`kind`) + + FfiConverterTypeFfiConversationMessageKind.allocationSize(value.`kind`) + FfiConverterTypeFfiDeliveryStatus.allocationSize(value.`deliveryStatus`) ) @@ -7311,7 +7662,7 @@ public object FfiConverterTypeFfiMessage : FfiConverterRustBuffer { FfiConverterByteArray.write(value.`convoId`, buf) FfiConverterString.write(value.`senderInboxId`, buf) FfiConverterByteArray.write(value.`content`, buf) - FfiConverterTypeFfiGroupMessageKind.write(value.`kind`, buf) + FfiConverterTypeFfiConversationMessageKind.write(value.`kind`, buf) FfiConverterTypeFfiDeliveryStatus.write(value.`deliveryStatus`, buf) } } @@ -7567,7 +7918,7 @@ public object FfiConverterTypeFfiV2SubscribeRequest : enum class FfiConsentEntityType { - GROUP_ID, + CONVERSATION_ID, INBOX_ID, ADDRESS; @@ -7615,50 +7966,51 @@ public object FfiConverterTypeFfiConsentState : FfiConverterRustBuffer { +public object FfiConverterTypeFfiConversationMessageKind : + FfiConverterRustBuffer { override fun read(buf: ByteBuffer) = try { - FfiDeliveryStatus.values()[buf.getInt() - 1] + FfiConversationMessageKind.values()[buf.getInt() - 1] } catch (e: IndexOutOfBoundsException) { throw RuntimeException("invalid enum value, something is very wrong!!", e) } - override fun allocationSize(value: FfiDeliveryStatus) = 4UL + override fun allocationSize(value: FfiConversationMessageKind) = 4UL - override fun write(value: FfiDeliveryStatus, buf: ByteBuffer) { + override fun write(value: FfiConversationMessageKind, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) } } -enum class FfiGroupMessageKind { +enum class FfiDeliveryStatus { - APPLICATION, - MEMBERSHIP_CHANGE; + UNPUBLISHED, + PUBLISHED, + FAILED; companion object } -public object FfiConverterTypeFfiGroupMessageKind : FfiConverterRustBuffer { +public object FfiConverterTypeFfiDeliveryStatus : FfiConverterRustBuffer { override fun read(buf: ByteBuffer) = try { - FfiGroupMessageKind.values()[buf.getInt() - 1] + FfiDeliveryStatus.values()[buf.getInt() - 1] } catch (e: IndexOutOfBoundsException) { throw RuntimeException("invalid enum value, something is very wrong!!", e) } - override fun allocationSize(value: FfiGroupMessageKind) = 4UL + override fun allocationSize(value: FfiDeliveryStatus) = 4UL - override fun write(value: FfiGroupMessageKind, buf: ByteBuffer) { + override fun write(value: FfiDeliveryStatus, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) } } @@ -7986,7 +8338,7 @@ public object FfiConverterTypeSigningError : FfiConverterRustBuffer uniffiObj.`onConversation`( - FfiConverterTypeFfiGroup.lift(`conversation`), + FfiConverterTypeFfiConversation.lift(`conversation`), ) } val writeReturn = { _: Unit -> Unit } @@ -8648,24 +9000,25 @@ public object FfiConverterSequenceByteArray : FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { +public object FfiConverterSequenceTypeFfiConversation : + FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { val len = buf.getInt() - return List(len) { - FfiConverterTypeFfiGroup.read(buf) + return List(len) { + FfiConverterTypeFfiConversation.read(buf) } } - override fun allocationSize(value: List): ULong { + override fun allocationSize(value: List): ULong { val sizeForLength = 4UL - val sizeForItems = value.map { FfiConverterTypeFfiGroup.allocationSize(it) }.sum() + val sizeForItems = value.map { FfiConverterTypeFfiConversation.allocationSize(it) }.sum() return sizeForLength + sizeForItems } - override fun write(value: List, buf: ByteBuffer) { + override fun write(value: List, buf: ByteBuffer) { buf.putInt(value.size) value.iterator().forEach { - FfiConverterTypeFfiGroup.write(it, buf) + FfiConverterTypeFfiConversation.write(it, buf) } } } @@ -8694,6 +9047,31 @@ public object FfiConverterSequenceTypeFfiConsent : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeFfiConversationMember.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = + value.map { FfiConverterTypeFfiConversationMember.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeFfiConversationMember.write(it, buf) + } + } +} + + public object FfiConverterSequenceTypeFfiEnvelope : FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): List { val len = buf.getInt() @@ -8717,25 +9095,24 @@ public object FfiConverterSequenceTypeFfiEnvelope : FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { +public object FfiConverterSequenceTypeFfiInboxState : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { val len = buf.getInt() - return List(len) { - FfiConverterTypeFfiGroupMember.read(buf) + return List(len) { + FfiConverterTypeFfiInboxState.read(buf) } } - override fun allocationSize(value: List): ULong { + override fun allocationSize(value: List): ULong { val sizeForLength = 4UL - val sizeForItems = value.map { FfiConverterTypeFfiGroupMember.allocationSize(it) }.sum() + val sizeForItems = value.map { FfiConverterTypeFfiInboxState.allocationSize(it) }.sum() return sizeForLength + sizeForItems } - override fun write(value: List, buf: ByteBuffer) { + override fun write(value: List, buf: ByteBuffer) { buf.putInt(value.size) value.iterator().forEach { - FfiConverterTypeFfiGroupMember.write(it, buf) + FfiConverterTypeFfiInboxState.write(it, buf) } } } diff --git a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so index 903a911c..18e61551 100644 Binary files a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so index 985c74d6..799afc98 100644 Binary files a/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so b/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so index 88d13cb1..8d56a912 100644 Binary files a/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so index 921fbff5..ee1d6075 100644 Binary files a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so differ