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 sdk support #508

Merged
merged 38 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
54be3f3
Merge pull request #500 from xmtp/np/update-beta-with-main
nplasterer Sep 24, 2024
4d9d018
bump the pod
nplasterer Oct 2, 2024
af4486b
Merge branch 'main' of https://github.com/xmtp/xmtp-react-native into…
nplasterer Oct 2, 2024
6383b9c
Merge pull request #507 from xmtp/np/merge-main-beta
nplasterer Oct 2, 2024
27e5aec
Merge branch 'beta' of https://github.com/xmtp/xmtp-react-native into…
nplasterer Oct 2, 2024
8c6a186
add chainId and isSmartContractWallet fields
nplasterer Sep 24, 2024
37b9271
add interface for signer
nplasterer Sep 24, 2024
268e461
pass the fields to the signer
nplasterer Sep 24, 2024
2c72d55
get the create the build
nplasterer Sep 24, 2024
6bb9427
Update signer
rygine Sep 26, 2024
1fa7b37
Update swift
rygine Sep 26, 2024
a10db52
cherry pick all the commits from the other PR
nplasterer Oct 2, 2024
f47bcc7
make this cleaner
nplasterer Oct 2, 2024
3dd9161
get on the latest version of the backend
nplasterer Oct 2, 2024
5100167
Android side
nplasterer Oct 3, 2024
9249818
allow block number to be null
nplasterer Oct 3, 2024
e00a62e
latest libxmtp
nplasterer Oct 11, 2024
c1ebdf2
update android and the methods
nplasterer Oct 20, 2024
f8a820c
try and get the iOS side matching
nplasterer Oct 20, 2024
1094e05
Merge branch 'main' of https://github.com/xmtp/xmtp-react-native into…
nplasterer Oct 20, 2024
3d8063d
point to the potential future gradle
nplasterer Oct 20, 2024
ed81adf
fix: optional field follows non-optional
peterferguson Oct 21, 2024
711f55f
Merge branch 'np/smart-contract-sdk-support' into sca-patch
peterferguson Oct 21, 2024
70461f4
fix chainId ternary
peterferguson Oct 21, 2024
1febd29
sig should be a string
peterferguson Oct 21, 2024
e7454f0
Merge pull request #515 from peterferguson/pf/np/smart-contract-sdk-s…
nplasterer Oct 22, 2024
4fc885c
change to wallet type and import latest libxmtp
nplasterer Oct 24, 2024
a6ca322
Merge branch 'np/smart-contract-sdk-support' of https://github.com/xm…
nplasterer Oct 24, 2024
1ccaec4
bad merge
nplasterer Oct 24, 2024
8ac19bb
do the ios side
nplasterer Oct 24, 2024
dc4de41
int instead of uint
nplasterer Oct 24, 2024
fd6e6d1
fix the signer
nplasterer Oct 24, 2024
b42a54b
get iOS compiling correctyl
nplasterer Oct 24, 2024
93bd230
Merge branch 'main' of https://github.com/xmtp/xmtp-react-native into…
nplasterer Oct 24, 2024
bf43701
add back limit
nplasterer Oct 24, 2024
230db03
fix: ios type <-> walletType field name mismatch
peterferguson Oct 24, 2024
74c82dd
Merge pull request #516 from peterferguson/pf/np/smart-contract-sdk-s…
nplasterer Oct 24, 2024
7d6d62d
feat: Smart Contract Wallet Support
nplasterer Oct 24, 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
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:0.15.12"
implementation "org.xmtp:android:0.16.0"
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.facebook.react:react-native:0.71.3'
implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.xmtp.android.library.PreEventCallback
import org.xmtp.android.library.PreparedMessage
import org.xmtp.android.library.SendOptions
import org.xmtp.android.library.SigningKey
import org.xmtp.android.library.WalletType
import org.xmtp.android.library.XMTPEnvironment
import org.xmtp.android.library.XMTPException
import org.xmtp.android.library.codecs.Attachment
Expand All @@ -55,6 +56,7 @@ import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.codecs.EncryptedEncodedContent
import org.xmtp.android.library.codecs.RemoteAttachment
import org.xmtp.android.library.codecs.decoded
import org.xmtp.android.library.hexToByteArray
import org.xmtp.android.library.messages.EnvelopeBuilder
import org.xmtp.android.library.messages.InvitationV1ContextBuilder
import org.xmtp.android.library.messages.MessageDeliveryStatus
Expand All @@ -69,7 +71,6 @@ import org.xmtp.proto.message.api.v1.MessageApiOuterClass
import org.xmtp.proto.message.contents.Invitation.ConsentProofPayload
import org.xmtp.proto.message.contents.PrivateKeyOuterClass
import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.GroupPermissionPreconfiguration
import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.InboxState
import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionOption
import java.io.BufferedReader
import java.io.File
Expand All @@ -80,8 +81,16 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

class ReactNativeSigner(var module: XMTPModule, override var address: String) : SigningKey {

class ReactNativeSigner(
var module: XMTPModule,
override var address: String,
override var type: WalletType = WalletType.EOA,
override var chainId: Long? = null,
override var blockNumber: Long? = null,
) : SigningKey {
private val continuations: MutableMap<String, Continuation<Signature>> = mutableMapOf()
private val scwContinuations: MutableMap<String, Continuation<ByteArray>> = mutableMapOf()

fun handle(id: String, signature: String) {
val continuation = continuations[id] ?: return
Expand All @@ -101,6 +110,20 @@ class ReactNativeSigner(var module: XMTPModule, override var address: String) :
continuations.remove(id)
}

fun handleSCW(id: String, signature: String) {
val continuation = scwContinuations[id] ?: return
continuation.resume(signature.hexToByteArray())
scwContinuations.remove(id)
}

override suspend fun signSCW(message: String): ByteArray {
val request = SignatureRequest(message = message)
module.sendEvent("sign", mapOf("id" to request.id, "message" to request.message))
return suspendCancellableCoroutine { continuation ->
scwContinuations[request.id] = continuation
}
}

override suspend fun sign(data: ByteArray): Signature {
val request = SignatureRequest(message = String(data, Charsets.UTF_8))
module.sendEvent("sign", mapOf("id" to request.id, "message" to request.message))
Expand Down Expand Up @@ -330,6 +353,11 @@ class XMTPModule : Module() {
signer?.handle(id = requestID, signature = signature)
}

Function("receiveSCWSignature") { requestID: String, signature: String ->
logV("receiveSCWSignature")
signer?.handleSCW(id = requestID, signature = signature)
}

// Generate a random wallet and set the client to that
AsyncFunction("createRandom") Coroutine { hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasPreAuthenticateToInboxCallback: Boolean?, dbEncryptionKey: List<Int>?, authParams: String ->
withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -375,10 +403,17 @@ class XMTPModule : Module() {
}
}

AsyncFunction("createOrBuild") Coroutine { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasAuthInboxCallback: Boolean?, dbEncryptionKey: List<Int>?, authParams: String ->
AsyncFunction("createV3") Coroutine { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasAuthInboxCallback: Boolean?, dbEncryptionKey: List<Int>?, authParams: String ->
withContext(Dispatchers.IO) {
logV("createOrBuild")
val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address)
logV("createV3")
val authOptions = AuthParamsWrapper.authParamsFromJson(authParams)
val reactSigner = ReactNativeSigner(
module = this@XMTPModule,
address = address,
type = authOptions.walletType,
chainId = authOptions.chainId,
blockNumber = authOptions.blockNumber
)
signer = reactSigner
val options = clientOptions(
dbEncryptionKey,
Expand All @@ -387,14 +422,29 @@ class XMTPModule : Module() {
hasEnableIdentityCallback,
hasAuthInboxCallback,
)
val client = Client().createOrBuild(account = reactSigner, options = options)
val client = Client().createV3(account = reactSigner, options = options)
clients[client.inboxId] = client
ContentJson.Companion
signer = null
sendEvent("authedV3", ClientWrapper.encodeToObj(client))
}
}

AsyncFunction("buildV3") Coroutine { address: String, dbEncryptionKey: List<Int>?, authParams: String ->
withContext(Dispatchers.IO) {
logV("buildV3")
val authOptions = AuthParamsWrapper.authParamsFromJson(authParams)
val options = clientOptions(
dbEncryptionKey,
authParams,
)
val client = Client().buildV3(address = address, options = options)
ContentJson.Companion
clients[client.inboxId] = client
ClientWrapper.encodeToObj(client)
}
}

AsyncFunction("createRandomV3") Coroutine { hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasPreAuthenticateToInboxCallback: Boolean?, dbEncryptionKey: List<Int>?, authParams: String ->
withContext(Dispatchers.IO) {
logV("createRandomV3")
Expand All @@ -406,7 +456,7 @@ class XMTPModule : Module() {
hasEnableIdentityCallback,
hasPreAuthenticateToInboxCallback,
)
val randomClient = Client().createOrBuild(account = privateKey, options = options)
val randomClient = Client().createV3(account = privateKey, options = options)

ContentJson.Companion
clients[randomClient.inboxId] = randomClient
Expand Down Expand Up @@ -635,7 +685,7 @@ class XMTPModule : Module() {
val sortedGroupList = if (order == ConversationOrder.LAST_MESSAGE) {
client.conversations.listGroups()
.sortedByDescending { group ->
group.decryptedMessages().firstOrNull()?.sentAt
group.decryptedMessages(limit = 1).firstOrNull()?.sentAt
}
.let { groups ->
if (limit != null && limit > 0) groups.take(limit) else groups
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.JsonParser
import org.xmtp.android.library.WalletType

class AuthParamsWrapper(
val environment: String,
val appVersion: String?,
val enableV3: Boolean = false,
val dbDirectory: String?,
val historySyncUrl: String?,
val walletType: WalletType = WalletType.EOA,
val chainId: Long?,
val blockNumber: Long?,
) {
companion object {
fun authParamsFromJson(authParams: String): AuthParamsWrapper {
Expand All @@ -17,8 +21,18 @@ class AuthParamsWrapper(
if (jsonOptions.has("appVersion")) jsonOptions.get("appVersion").asString else null,
if (jsonOptions.has("enableV3")) jsonOptions.get("enableV3").asBoolean else false,
if (jsonOptions.has("dbDirectory")) jsonOptions.get("dbDirectory").asString else null,
if (jsonOptions.has("historySyncUrl")) jsonOptions.get("historySyncUrl").asString else null
)
if (jsonOptions.has("historySyncUrl")) jsonOptions.get("historySyncUrl").asString else null,
if (jsonOptions.has("walletType")) {
when (jsonOptions.get("walletType").asString) {
"SCW" -> WalletType.SCW
else -> WalletType.EOA
}
} else {
WalletType.EOA
},
if (jsonOptions.has("chainId")) jsonOptions.get("chainId").asLong else null,
if (jsonOptions.has("blockNumber")) jsonOptions.get("blockNumber").asLong else null,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GroupWrapper {
put("consentState", consentStateToString(group.consentState()))
}
if (groupParams.lastMessage) {
val lastMessage = group.decryptedMessages().firstOrNull()
val lastMessage = group.decryptedMessages(limit = 1).firstOrNull()
if (lastMessage != null) {
put("lastMessage", DecodedMessageWrapper.encode(lastMessage))
}
Expand Down
14 changes: 7 additions & 7 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ PODS:
- hermes-engine/Pre-built (= 0.71.14)
- hermes-engine/Pre-built (0.71.14)
- libevent (2.1.12)
- LibXMTP (0.5.9-beta0)
- LibXMTP (0.5.10)
- Logging (1.0.0)
- MessagePacker (0.4.7)
- MMKV (2.0.0):
Expand Down Expand Up @@ -449,16 +449,16 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.15.0):
- XMTP (0.15.2):
- Connect-Swift (= 0.12.0)
- GzipSwift
- LibXMTP (= 0.5.9-beta0)
- LibXMTP (= 0.5.10)
- web3.swift
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- secp256k1.swift
- XMTP (= 0.15.0)
- XMTP (= 0.15.2)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -711,7 +711,7 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: 5a38722a68a9469be2e711857a5e7d9dd3aa8a61
LibXMTP: 3b64b0b1e4157ff73c37cde60fe943f89e6f8693
Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801
Expand Down Expand Up @@ -763,8 +763,8 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 09faa347569b092005997364f7fe787ccc33f3d5
XMTPReactNative: 6404c11e6dd11820742d4af899daeea389fc442f
XMTP: 7d47e6bc507db66dd01116ce2b4ed04dd3560a4f
XMTPReactNative: 1a946cd697598fb4bc560a637094e63c4d553df3
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 0e6fe50018f34e575d38dc6a1fdf1f99c9596cdd
Expand Down
15 changes: 13 additions & 2 deletions example/src/tests/v3OnlyTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,20 @@ test('can make a V3 only client', async () => {
client.inboxId === inboxId,
`inboxIds should match but were ${client.inboxId} and ${inboxId}`
)
const canMessageV3 = await client.canGroupMessage([client.address])

const client2 = await Client.buildV3(client.address, {
env: 'local',
appVersion: 'Testing/0.0.0',
enableV3: true,
dbEncryptionKey: keyBytes,
})

assert(
client.inboxId === client2.inboxId,
`inboxIds should match but were ${client.inboxId} and ${client2.inboxId}`
)

const canMessageV3 = await client.canGroupMessage([client.address])
assert(
canMessageV3[client.address.toLowerCase()] === true,
`canMessageV3 should be true`
Expand All @@ -51,7 +63,6 @@ test('can make a V3 only client', async () => {
} catch (error) {
return true
}

throw new Error('should throw error when hitting V2 api')
})

Expand Down
31 changes: 30 additions & 1 deletion ios/ReactNativeSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ class ReactNativeSigner: NSObject, XMTP.SigningKey {

var module: XMTPModule
var address: String
var type: WalletType
var chainId: Int64?
var blockNumber: Int64?
var continuations: [String: CheckedContinuation<XMTP.Signature, Swift.Error>] = [:]
var scwContinuations: [String: CheckedContinuation<Data, Swift.Error>] = [:]

init(module: XMTPModule, address: String) {
init(module: XMTPModule, address: String, walletType: WalletType = WalletType.EOA, chainId: Int64? = nil, blockNumber: Int64? = nil) {
self.module = module
self.address = address
self.type = walletType
self.chainId = chainId
self.blockNumber = blockNumber
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the block number to be stored on the signer. That should get resolved at the time of signing, not on instantiation, since the block number keeps incrementing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We store it on the SigningKey so it only gets passed in from the SigningKey which means we only have it when the signing key is present which is only when something is being signed. Unfortunately with the bridge this is how it has to be passed. But this and the below shouldn't be exposed to users so it should be an internal matter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I can approve the PR. Still feels wrong to get that block number even one second before we actually sign the message, since it's going to be older than the block the signature was actually generated on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm okay maybe we need to rethink how I have this setup inside the other sdks as well. 🤔

As they all work the same with the block number as part of the signingKey https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/SigningKey.kt#L31-L34 But maybe it should be passed as a param to create instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally it would get pulled from the chain at the same time you are signing the message.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way of thinking about it would be to have a getBlockNumber method that is only implemented on SCWs, which we could call from our SDKs when we are gathering a smart contract signature.

}

func handle(id: String, signature: String) throws {
Expand All @@ -40,6 +47,28 @@ class ReactNativeSigner: NSObject, XMTP.SigningKey {
continuation.resume(returning: signature)
continuations.removeValue(forKey: id)
}

func handleSCW(id: String, signature: String) throws {
guard let continuation = scwContinuations[id] else {
return
}

continuation.resume(returning: signature.hexToData)
scwContinuations.removeValue(forKey: id)
}

func signSCW(message: String) async throws -> Data {
let request = SignatureRequest(message: message)

module.sendEvent("sign", [
"id": request.id,
"message": request.message,
])

return try await withCheckedThrowingContinuation { continuation in
scwContinuations[request.id] = continuation
}
}

func sign(_ data: Data) async throws -> XMTP.Signature {
let request = SignatureRequest(message: String(data: data, encoding: .utf8)!)
Expand Down
29 changes: 25 additions & 4 deletions ios/Wrappers/AuthParamsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,54 @@ struct AuthParamsWrapper {
let enableV3: Bool
let dbDirectory: String?
let historySyncUrl: String?

init(environment: String, appVersion: String?, enableV3: Bool, dbDirectory: String?, historySyncUrl: String?) {
let walletType: WalletType
let chainId: Int64?
let blockNumber: Int64?

init(environment: String, appVersion: String?, enableV3: Bool, dbDirectory: String?, historySyncUrl: String?, walletType: WalletType, chainId: Int64?, blockNumber: Int64?) {
self.environment = environment
self.appVersion = appVersion
self.enableV3 = enableV3
self.dbDirectory = dbDirectory
self.historySyncUrl = historySyncUrl
self.walletType = walletType
self.chainId = chainId
self.blockNumber = blockNumber
}

static func authParamsFromJson(_ authParams: String) -> AuthParamsWrapper {
guard let data = authParams.data(using: .utf8),
let jsonOptions = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return AuthParamsWrapper(environment: "dev", appVersion: nil, enableV3: false, dbDirectory: nil, historySyncUrl: nil)
return AuthParamsWrapper(environment: "dev", appVersion: nil, enableV3: false, dbDirectory: nil, historySyncUrl: nil, walletType: WalletType.EOA, chainId: nil, blockNumber: nil)
}

let environment = jsonOptions["environment"] as? String ?? "dev"
let appVersion = jsonOptions["appVersion"] as? String
let enableV3 = jsonOptions["enableV3"] as? Bool ?? false
let dbDirectory = jsonOptions["dbDirectory"] as? String
let historySyncUrl = jsonOptions["historySyncUrl"] as? String
let walletTypeString = jsonOptions["walletType"] as? String ?? "EOA"
let chainId = jsonOptions["chainId"] as? Int64
let blockNumber = jsonOptions["blockNumber"] as? Int64

let walletType = { switch walletTypeString {
case "SCW":
return WalletType.SCW
default:
return WalletType.EOA
}
}()


return AuthParamsWrapper(
environment: environment,
appVersion: appVersion,
enableV3: enableV3,
dbDirectory: dbDirectory,
historySyncUrl: historySyncUrl
historySyncUrl: historySyncUrl,
walletType: walletType,
chainId: chainId,
blockNumber: blockNumber
)
}
}
2 changes: 1 addition & 1 deletion ios/Wrappers/GroupWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct GroupWrapper {
result["consentState"] = ConsentWrapper.consentStateToString(state: try group.consentState())
}
if groupParams.lastMessage {
if let lastMessage = try await group.decryptedMessages().first {
if let lastMessage = try await group.decryptedMessages(limit: 1).first {
result["lastMessage"] = try DecodedMessageWrapper.encode(lastMessage, client: client)
}
}
Expand Down
Loading
Loading