1+ /*
2+ * Copyright 2025 Circle Internet Group, Inc. All rights reserved.
3+ *
4+ * SPDX-License-Identifier: Apache-2.0.
5+ *
6+ * Licensed under the Apache License, Version 2.0 (the "License");
7+ * you may not use this file except in compliance with the License.
8+ * You may obtain a copy of the License at.
9+ *
10+ * Http://www.apache.org/licenses/LICENSE-2.0.
11+ *
12+ * Unless required by applicable law or agreed to in writing, software
13+ * distributed under the License is distributed on an "AS IS" BASIS,
14+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+ * See the License for the specific language governing permissions and
16+ * limitations under the License.
17+ */
18+
19+ package com.circle.modularwallets.core.accounts.implementations
20+
21+ import android.content.Context
22+ import com.circle.modularwallets.core.accounts.LocalAccount
23+ import com.circle.modularwallets.core.apis.modular.ModularApiImpl
24+ import com.circle.modularwallets.core.apis.modular.ModularWallet
25+ import com.circle.modularwallets.core.apis.modular.ScaConfiguration
26+ import com.circle.modularwallets.core.apis.modular.getCreateWalletReq
27+ import com.circle.modularwallets.core.apis.util.UtilApiImpl
28+ import com.circle.modularwallets.core.clients.Client
29+ import com.circle.modularwallets.core.constants.CIRCLE_WEIGHTED_WEB_AUTHN_MULTISIG_PLUGIN
30+ import com.circle.modularwallets.core.constants.FACTORY
31+ import com.circle.modularwallets.core.constants.OWNER_WEIGHT
32+ import com.circle.modularwallets.core.constants.SALT
33+ import com.circle.modularwallets.core.constants.SIG_TYPE_FLAG_DIGEST
34+ import com.circle.modularwallets.core.constants.THRESHOLD_WEIGHT
35+ import com.circle.modularwallets.core.transports.Transport
36+ import com.circle.modularwallets.core.utils.abi.encodeAbiParameters
37+ import com.circle.modularwallets.core.utils.abi.encodePacked
38+ import com.circle.modularwallets.core.utils.data.pad
39+ import com.circle.modularwallets.core.utils.signature.deserializeSignature
40+ import org.web3j.abi.FunctionEncoder
41+ import org.web3j.abi.TypeReference
42+ import org.web3j.abi.datatypes.Address
43+ import org.web3j.abi.datatypes.DynamicArray
44+ import org.web3j.abi.datatypes.DynamicBytes
45+ import org.web3j.abi.datatypes.StaticStruct
46+ import org.web3j.abi.datatypes.Type
47+ import org.web3j.abi.datatypes.generated.Bytes32
48+ import org.web3j.abi.datatypes.generated.Uint256
49+ import org.web3j.abi.datatypes.generated.Uint8
50+ import org.web3j.crypto.Sign
51+ import org.web3j.utils.Numeric
52+
53+ internal class LocalCircleSmartAccountDelegate (val owner : LocalAccount ) :
54+ CircleSmartAccountDelegate {
55+
56+ override suspend fun getModularWalletAddress (
57+ transport : Transport ,
58+ version : String ,
59+ name : String?
60+ ): ModularWallet {
61+ return getModularWalletAddress(transport, owner.getAddress(), version, name)
62+ }
63+
64+ override suspend fun getComputeWallet (client : Client , version : String ): ModularWallet {
65+ return ModularWallet (
66+ address = getAddressFromLocalOwner(client.transport, owner.getAddress()),
67+ scaConfiguration = ScaConfiguration (
68+ scaCore = version,
69+ ),
70+ )
71+ }
72+
73+ override fun getFactoryData (): String {
74+ return getFactoryData(owner.getAddress())
75+ }
76+
77+ override suspend fun signAndWrap (
78+ context : Context ,
79+ hash : String ,
80+ hasUserOpGas : Boolean
81+ ): String {
82+ val signature =
83+ if (hasUserOpGas) owner.signMessage(context, hash) else owner.sign(context, hash)
84+ val signatureData = deserializeSignature(signature)
85+ return encodePackedForSignature(signatureData, hasUserOpGas)
86+ }
87+
88+ companion object {
89+ const val WALLET_PREFIX = " wallet"
90+ internal suspend fun getModularWalletAddress (
91+ transport : Transport , address : String , version : String , name : String? = null
92+ ): ModularWallet {
93+ val wallet =
94+ ModularApiImpl .getAddress(
95+ transport,
96+ getCreateWalletReq(address, version, name)
97+ )
98+ return wallet
99+ }
100+
101+ internal suspend fun getAddressFromLocalOwner (
102+ transport : Transport ,
103+ address : String
104+ ): String {
105+ val sender = pad(address)
106+ val initializeUpgradableMSCAParams = getInitializeUpgradableMSCAParams(address)
107+
108+ /* * address, mixedSalt */
109+ val result = UtilApiImpl .getAddress(
110+ transport,
111+ FACTORY .address,
112+ Numeric .hexStringToByteArray(sender),
113+ SALT ,
114+ Numeric .hexStringToByteArray(initializeUpgradableMSCAParams)
115+ )
116+ return result.first
117+ }
118+
119+ private fun getInitializeUpgradableMSCAParams (address : String ): String {
120+ val pluginInstallParams = getPluginInstallParams(address)
121+ val encoded = encodeAbiParameters(
122+ listOf (
123+ DynamicArray (
124+ Address ::class .java,
125+ Address (CIRCLE_WEIGHTED_WEB_AUTHN_MULTISIG_PLUGIN .address),
126+ ),
127+ DynamicArray (
128+ Bytes32 ::class .java,
129+ Bytes32 (CIRCLE_WEIGHTED_WEB_AUTHN_MULTISIG_PLUGIN .manifestHash),
130+ ),
131+ DynamicArray (
132+ DynamicBytes ::class .java,
133+ DynamicBytes (Numeric .hexStringToByteArray(pluginInstallParams)),
134+ ),
135+ )
136+ )
137+ return encoded
138+ }
139+
140+ private fun getPluginInstallParams (address : String ): String {
141+ val encoded = encodeAbiParameters(
142+ listOf (
143+ DynamicArray (Address ::class .java, Address (address)),
144+ DynamicArray (
145+ Uint256 ::class .java, Uint256 (OWNER_WEIGHT )
146+ ),
147+ DynamicArray (StaticStruct ::class .java),
148+ DynamicArray (Uint256 ::class .java),
149+ Uint256 (THRESHOLD_WEIGHT )
150+ )
151+ )
152+ return encoded
153+ }
154+
155+ private fun getFactoryData (address : String ): String {
156+ val sender = pad(address)
157+ val initializeUpgradableMSCAParams = getInitializeUpgradableMSCAParams(address)
158+ val function = org.web3j.abi.datatypes.Function (
159+ " createAccount" , listOf (
160+ Bytes32 (Numeric .hexStringToByteArray(sender)),
161+ Bytes32 (SALT ),
162+ DynamicBytes (Numeric .hexStringToByteArray(initializeUpgradableMSCAParams)),
163+ ), listOf<TypeReference <* >>(object : TypeReference <Address >() {})
164+ )
165+ val factoryData = FunctionEncoder .encode(function)
166+ return factoryData
167+ }
168+
169+ /* *
170+ * Wraps a raw Secp256k1 signature into the ABI-encoded format expected by smart contract.
171+ * */
172+ internal fun encodePackedForSignature (
173+ signatureData : Sign .SignatureData ,
174+ hasUserOpGas : Boolean
175+ ): String {
176+ val sigType: Long =
177+ if (hasUserOpGas) signatureData.v[0 ].toLong() + SIG_TYPE_FLAG_DIGEST else signatureData.v[0 ].toLong()
178+ val encoded =
179+ encodePacked(
180+ listOf<Type <* >>(
181+ Bytes32 (signatureData.r),
182+ Bytes32 (signatureData.s),
183+ Uint8 (sigType),
184+ )
185+ )
186+
187+ return encoded
188+ }
189+ }
190+ }
0 commit comments