Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart contract wallet support #304

Merged
merged 33 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c562acf
bump version
nplasterer Oct 3, 2024
1016340
get on the latest bindings
nplasterer Oct 3, 2024
c8bccbd
add the defaults
nplasterer Oct 3, 2024
062f1fa
bump version
nplasterer Oct 3, 2024
13abd5e
Fix kt lint error that was blocking build
Koleok Oct 3, 2024
96ef790
allow block number to be optional
nplasterer Oct 3, 2024
6f55da2
Merge branch 'np/smart-contract-wallet-support' of https://github.com…
nplasterer Oct 3, 2024
28a2493
resolve conflicts
nplasterer Oct 8, 2024
39f3667
bump version again
nplasterer Oct 9, 2024
32e00b2
write a test for it
nplasterer Oct 9, 2024
597e9f4
probably just want the bytes that were passed directly
nplasterer Oct 9, 2024
d3e5789
bump
nplasterer Oct 11, 2024
2cc2d86
bump the lib as well
nplasterer Oct 11, 2024
71b776b
add real smart contract wallet test
nplasterer Oct 13, 2024
8f80866
add the binary files
nplasterer Oct 13, 2024
a268e44
need to fix the signature issue
nplasterer Oct 13, 2024
075f395
make the signing key optional
nplasterer Oct 16, 2024
594e59e
get on the latest version
nplasterer Oct 17, 2024
3c1304e
new binaries
nplasterer Oct 17, 2024
89561ff
a few more tweaks to the sign functions
nplasterer Oct 17, 2024
6ae1343
maybe getting closer
nplasterer Oct 17, 2024
d8e447a
Fix failing SCW test
neekolas Oct 17, 2024
2d21a59
Fix anvil command
neekolas Oct 17, 2024
1841229
dump the bindings again
nplasterer Oct 18, 2024
ff9c5cf
update the client to create and a seperate to build
nplasterer Oct 18, 2024
1e7f8d7
get the tests cleaned up
nplasterer Oct 18, 2024
a0187e4
remove the read me
nplasterer Oct 18, 2024
15488ce
dumpt he v
nplasterer Oct 18, 2024
a252ccd
make optional
nplasterer Oct 18, 2024
34ab0c1
get all the tests working
nplasterer Oct 19, 2024
0de78f8
fix the linter
nplasterer Oct 19, 2024
7e53138
rename
nplasterer Oct 19, 2024
e8647e4
add other types to the wallet for future identities
nplasterer Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions dev/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions library/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 type: WalletType
get() = WalletType.SCW

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class V3ClientTest {
boV3Wallet = PrivateKeyBuilder()
boV3 = boV3Wallet.getPrivateKey()
boV3Client = runBlocking {
Client().createOrBuild(
Client().createV3(
account = boV3Wallet,
options = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
Expand Down

Large diffs are not rendered by default.

Loading
Loading