feat: 支持 Magic(邮箱/社交登录)账户导入和赎回
- 前端:启用 Magic 账户导入选项(移除 disabled 和"暂不支持"提示) - 订单签名:根据钱包类型自动设置 signatureType(Magic=1, Safe=2) - Builder Relayer API:添加 relay-payload 接口和 PROXY 请求类型支持 - RelayClientService:实现 Magic PROXY 赎回流程(Gasless,需 Builder API Key) - 获取 relay payload(relay 地址和 nonce) - 编码 ProxyFactory.proxy(calls) 调用数据 - 创建并签名 PROXY 结构哈希 - 提交 PROXY 类型交易到 Builder Relayer - BlockchainService:redeemPositions 支持 walletType 参数 - AccountService:移除 Magic 账户赎回限制,传递 walletType 参数 Magic 账户赎回要求配置 Builder API Key(Gasless),Safe 账户保持原有逻辑不变。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ backend/gradle-app.setting
|
||||
backend/.gradle
|
||||
# 注意:gradle-wrapper.jar 应该被提交,不要忽略
|
||||
# backend/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
polyhub/
|
||||
# Kotlin
|
||||
*.kt.bak
|
||||
*.class
|
||||
|
||||
@@ -41,6 +41,17 @@ interface BuilderRelayerApi {
|
||||
@Query("address") address: String,
|
||||
@Query("type") type: String
|
||||
): Response<NoncePayload>
|
||||
|
||||
/**
|
||||
* 获取 Relay Payload(PROXY 类型执行时使用)
|
||||
* GET /relay-payload?address={address}&type=PROXY
|
||||
* 参考: builder-relayer-client endpoints GET_RELAY_PAYLOAD
|
||||
*/
|
||||
@GET("/relay-payload")
|
||||
suspend fun getRelayPayload(
|
||||
@Query("address") address: String,
|
||||
@Query("type") type: String
|
||||
): Response<RelayPayload>
|
||||
|
||||
/**
|
||||
* 获取交易状态
|
||||
@@ -96,6 +107,7 @@ interface BuilderRelayerApi {
|
||||
/**
|
||||
* 签名参数
|
||||
* 参考: builder-relayer-client/src/types.ts 的 SignatureParams
|
||||
* Safe 使用 operation/safeTxnGas/baseGas 等,PROXY 使用 relayHub/relay/relayerFee 等
|
||||
*/
|
||||
data class SignatureParams(
|
||||
@SerializedName("gasPrice")
|
||||
@@ -114,7 +126,19 @@ interface BuilderRelayerApi {
|
||||
val gasToken: String? = null,
|
||||
|
||||
@SerializedName("refundReceiver")
|
||||
val refundReceiver: String? = null
|
||||
val refundReceiver: String? = null,
|
||||
|
||||
@SerializedName("relayerFee")
|
||||
val relayerFee: String? = null,
|
||||
|
||||
@SerializedName("gasLimit")
|
||||
val gasLimit: String? = null,
|
||||
|
||||
@SerializedName("relayHub")
|
||||
val relayHub: String? = null,
|
||||
|
||||
@SerializedName("relay")
|
||||
val relay: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -142,6 +166,17 @@ interface BuilderRelayerApi {
|
||||
@SerializedName("nonce")
|
||||
val nonce: String
|
||||
)
|
||||
|
||||
/**
|
||||
* Relay Payload(PROXY 执行时获取 relay 地址与 nonce)
|
||||
* 参考: builder-relayer-client types RelayPayload
|
||||
*/
|
||||
data class RelayPayload(
|
||||
@SerializedName("address")
|
||||
val address: String,
|
||||
@SerializedName("nonce")
|
||||
val nonce: String
|
||||
)
|
||||
|
||||
/**
|
||||
* Relayer 交易详情
|
||||
|
||||
@@ -827,7 +827,7 @@ class AccountService(
|
||||
"0"
|
||||
}
|
||||
|
||||
// 11. 创建并签名订单(使用计算后的卖出数量)
|
||||
// 11. 创建并签名订单(使用计算后的卖出数量,按账户钱包类型使用对应 signatureType)
|
||||
val signedOrder = try {
|
||||
orderSigningService.createAndSignOrder(
|
||||
privateKey = decryptedPrivateKey,
|
||||
@@ -836,7 +836,7 @@ class AccountService(
|
||||
side = "SELL",
|
||||
price = sellPrice,
|
||||
size = sellQuantity.toPlainString(), // 使用计算后的卖出数量
|
||||
signatureType = 2, // Browser Wallet(与正确订单数据一致)
|
||||
signatureType = orderSigningService.getSignatureTypeForWalletType(account.walletType),
|
||||
nonce = "0",
|
||||
feeRateBps = feeRateBps, // 使用动态获取的费率
|
||||
expiration = expiration
|
||||
@@ -1260,7 +1260,7 @@ class AccountService(
|
||||
accountRedeemedInfo[accountId] = accountInfo
|
||||
}
|
||||
|
||||
// 5. 对每个账户执行赎回
|
||||
// 5. 对每个账户执行赎回(Safe 与 Magic 均支持,Magic 通过 Builder Relayer PROXY Gasless 执行)
|
||||
val accountTransactions = mutableListOf<com.wrbug.polymarketbot.dto.AccountRedeemTransaction>()
|
||||
var totalRedeemedValue = BigDecimal.ZERO
|
||||
|
||||
@@ -1284,7 +1284,8 @@ class AccountService(
|
||||
privateKey = decryptedPrivateKey,
|
||||
proxyAddress = account.proxyAddress,
|
||||
conditionId = marketId,
|
||||
indexSets = indexSets
|
||||
indexSets = indexSets,
|
||||
walletType = account.walletType
|
||||
)
|
||||
|
||||
redeemResult.fold(
|
||||
|
||||
+8
-12
@@ -580,39 +580,35 @@ class BlockchainService(
|
||||
|
||||
/**
|
||||
* 赎回仓位
|
||||
* 通过代理钱包的 execTransaction 调用 ConditionalTokens 合约的 redeemPositions 函数
|
||||
*
|
||||
* 使用 RelayClientService 实现,完全参考 TypeScript 项目的实现方式
|
||||
*
|
||||
* Safe 账户通过代理 execTransaction 调用,Magic 账户通过 Builder Relayer PROXY(Gasless)执行
|
||||
*
|
||||
* @param privateKey 私钥(原始钱包的私钥,用于签名交易)
|
||||
* @param proxyAddress 代理地址(Gnosis Safe 代理钱包地址)
|
||||
* @param proxyAddress 代理地址(Safe 或 Magic 代理钱包地址)
|
||||
* @param conditionId 市场条件ID(bytes32,必须是 0x 开头的 66 位十六进制字符串)
|
||||
* @param indexSets 要赎回的索引集合列表(每个元素是 2^outcomeIndex,例如 [1] 表示 outcome 0,[2] 表示 outcome 1)
|
||||
* @param indexSets 要赎回的索引集合列表(每个元素是 2^outcomeIndex)
|
||||
* @param walletType 钱包类型:"magic" 或 "safe",用于选择执行路径
|
||||
* @return 交易哈希
|
||||
*/
|
||||
suspend fun redeemPositions(
|
||||
privateKey: String,
|
||||
proxyAddress: String,
|
||||
conditionId: String,
|
||||
indexSets: List<BigInteger>
|
||||
indexSets: List<BigInteger>,
|
||||
walletType: String = "safe"
|
||||
): Result<String> {
|
||||
return try {
|
||||
// 验证参数
|
||||
if (indexSets.isEmpty()) {
|
||||
return Result.failure(IllegalArgumentException("indexSets 不能为空"))
|
||||
}
|
||||
|
||||
if (conditionId.isBlank() || !conditionId.startsWith("0x") || conditionId.length != 66) {
|
||||
return Result.failure(IllegalArgumentException("conditionId 格式错误,必须是 0x 开头的 66 位十六进制字符串"))
|
||||
}
|
||||
|
||||
if (proxyAddress.isBlank() || !proxyAddress.startsWith("0x") || proxyAddress.length != 42) {
|
||||
return Result.failure(IllegalArgumentException("proxyAddress 格式错误,必须是有效的以太坊地址"))
|
||||
}
|
||||
|
||||
// 使用 RelayClientService 创建赎回交易并执行
|
||||
val redeemTx = relayClientService.createRedeemTx(conditionId, indexSets)
|
||||
relayClientService.execute(privateKey, proxyAddress, redeemTx)
|
||||
relayClientService.execute(privateKey, proxyAddress, redeemTx, walletType)
|
||||
} catch (e: Exception) {
|
||||
logger.error("赎回仓位失败: ${e.message}", e)
|
||||
Result.failure(e)
|
||||
|
||||
+10
-2
@@ -19,9 +19,17 @@ import java.math.RoundingMode
|
||||
*/
|
||||
@Service
|
||||
class OrderSigningService {
|
||||
|
||||
|
||||
private val logger = LoggerFactory.getLogger(OrderSigningService::class.java)
|
||||
|
||||
|
||||
/**
|
||||
* 根据钱包类型返回 CLOB 订单签名类型
|
||||
* @param walletType magic=邮箱/社交登录, safe=Web3 钱包
|
||||
* @return 1=POLY_PROXY(Magic), 2=POLY_GNOSIS_SAFE(Safe), 默认 2
|
||||
*/
|
||||
fun getSignatureTypeForWalletType(walletType: String?): Int =
|
||||
if (walletType?.lowercase() == "magic") 1 else 2
|
||||
|
||||
// Polygon 主网合约地址
|
||||
private val EXCHANGE_CONTRACT = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
|
||||
private val CHAIN_ID = 137L
|
||||
|
||||
+10
-6
@@ -570,7 +570,8 @@ open class CopyOrderTrackingService(
|
||||
owner = account.apiKey,
|
||||
copyTradingId = copyTrading.id!!,
|
||||
tradeId = trade.id,
|
||||
feeRateBps = feeRateBps
|
||||
feeRateBps = feeRateBps,
|
||||
signatureType = orderSigningService.getSignatureTypeForWalletType(account.walletType)
|
||||
)
|
||||
|
||||
// 处理订单创建失败
|
||||
@@ -994,7 +995,7 @@ open class CopyOrderTrackingService(
|
||||
"0"
|
||||
}
|
||||
|
||||
// 9. 创建并签名卖出订单
|
||||
// 9. 创建并签名卖出订单(按账户钱包类型使用对应 signatureType)
|
||||
val signedOrder = try {
|
||||
orderSigningService.createAndSignOrder(
|
||||
privateKey = decryptedPrivateKey,
|
||||
@@ -1003,7 +1004,7 @@ open class CopyOrderTrackingService(
|
||||
side = "SELL",
|
||||
price = sellPrice.toString(),
|
||||
size = totalMatched.toString(),
|
||||
signatureType = 2, // Browser Wallet
|
||||
signatureType = orderSigningService.getSignatureTypeForWalletType(account.walletType),
|
||||
nonce = "0",
|
||||
feeRateBps = feeRateBps, // 使用动态获取的费率
|
||||
expiration = "0"
|
||||
@@ -1044,7 +1045,8 @@ open class CopyOrderTrackingService(
|
||||
owner = account.apiKey,
|
||||
copyTradingId = copyTrading.id,
|
||||
tradeId = leaderSellTrade.id,
|
||||
feeRateBps = feeRateBps
|
||||
feeRateBps = feeRateBps,
|
||||
signatureType = orderSigningService.getSignatureTypeForWalletType(account.walletType)
|
||||
)
|
||||
|
||||
if (createOrderResult.isFailure) {
|
||||
@@ -1137,6 +1139,7 @@ open class CopyOrderTrackingService(
|
||||
* @param copyTradingId 跟单配置ID(用于日志)
|
||||
* @param tradeId Leader 交易ID(用于日志)
|
||||
* @param feeRateBps 费率基点(从API动态获取)
|
||||
* @param signatureType 签名类型(1=Magic, 2=Safe)
|
||||
* @return 成功返回订单ID,失败返回异常
|
||||
*/
|
||||
private suspend fun createOrderWithRetry(
|
||||
@@ -1150,7 +1153,8 @@ open class CopyOrderTrackingService(
|
||||
owner: String,
|
||||
copyTradingId: Long,
|
||||
tradeId: String,
|
||||
feeRateBps: String
|
||||
feeRateBps: String,
|
||||
signatureType: Int
|
||||
): Result<String> {
|
||||
var lastError: Exception? = null
|
||||
|
||||
@@ -1165,7 +1169,7 @@ open class CopyOrderTrackingService(
|
||||
side = side,
|
||||
price = price,
|
||||
size = size,
|
||||
signatureType = 2, // Browser Wallet
|
||||
signatureType = signatureType,
|
||||
nonce = "0",
|
||||
feeRateBps = feeRateBps, // 使用动态获取的费率
|
||||
expiration = "0"
|
||||
|
||||
+178
-10
@@ -40,6 +40,11 @@ class RelayClientService(
|
||||
// 空集合ID
|
||||
private val EMPTY_SET = "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// Polygon PROXY(Magic)合约地址,参考 builder-relayer-client config
|
||||
private val proxyFactoryAddress = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052"
|
||||
private val relayHubAddress = "0xD216153c06E857cD7f72665E0aF1d7D82172F494"
|
||||
private val defaultProxyGasLimit = "10000000"
|
||||
|
||||
private val polygonRpcApi: EthereumRpcApi by lazy {
|
||||
val rpcUrl = rpcNodeService.getHttpUrl()
|
||||
retrofitFactory.createEthereumRpcApi(rpcUrl)
|
||||
@@ -201,33 +206,45 @@ class RelayClientService(
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Safe 交易(通过 Proxy.execTransaction)
|
||||
* 执行代理交易(Safe 或 Magic PROXY)
|
||||
* 参考 TypeScript: RelayClient.execute()
|
||||
*
|
||||
* 优先使用 Builder Relayer(Gasless),如果未配置则回退到手动发送交易
|
||||
*
|
||||
* @param privateKey 私钥
|
||||
* @param proxyAddress 代理钱包地址
|
||||
* @param safeTx Safe 交易对象
|
||||
* @param safeTx 交易对象(to/data/value)
|
||||
* @param walletType 钱包类型:"magic" 使用 PROXY Gasless,"safe" 使用 Safe 流程
|
||||
* @return 交易哈希
|
||||
*/
|
||||
suspend fun execute(
|
||||
privateKey: String,
|
||||
proxyAddress: String,
|
||||
safeTx: SafeTransaction
|
||||
safeTx: SafeTransaction,
|
||||
walletType: String = "safe"
|
||||
): Result<String> {
|
||||
return try {
|
||||
// 验证参数
|
||||
if (proxyAddress.isBlank() || !proxyAddress.startsWith("0x") || proxyAddress.length != 42) {
|
||||
return Result.failure(IllegalArgumentException("proxyAddress 格式错误,必须是有效的以太坊地址"))
|
||||
}
|
||||
|
||||
// 检查 Builder API Key 是否已配置
|
||||
val builderApiKey = systemConfigService.getBuilderApiKey()
|
||||
val builderSecret = systemConfigService.getBuilderSecret()
|
||||
val builderPassphrase = systemConfigService.getBuilderPassphrase()
|
||||
|
||||
// 优先使用 Builder Relayer(Gasless)
|
||||
if (walletType.lowercase() == "magic") {
|
||||
if (!isBuilderRelayerEnabled(builderApiKey, builderSecret, builderPassphrase)) {
|
||||
return Result.failure(IllegalStateException("Magic 账户赎回必须配置 Builder API Key(Gasless)"))
|
||||
}
|
||||
logger.info("使用 Builder Relayer PROXY 执行 Magic 赎回")
|
||||
return executeViaBuilderRelayerProxy(
|
||||
privateKey,
|
||||
proxyAddress,
|
||||
safeTx,
|
||||
builderApiKey!!,
|
||||
builderSecret!!,
|
||||
builderPassphrase!!
|
||||
)
|
||||
}
|
||||
|
||||
if (isBuilderRelayerEnabled(builderApiKey, builderSecret, builderPassphrase)) {
|
||||
logger.info("使用 Builder Relayer 执行 Gasless 交易")
|
||||
return executeViaBuilderRelayer(
|
||||
@@ -240,15 +257,166 @@ class RelayClientService(
|
||||
)
|
||||
}
|
||||
|
||||
// 回退到手动发送交易(需要用户支付 gas)
|
||||
logger.info("Builder Relayer 未配置,使用手动发送交易(需要用户支付 gas)")
|
||||
return executeManually(privateKey, proxyAddress, safeTx)
|
||||
} catch (e: Exception) {
|
||||
logger.error("执行 Safe 交易失败: ${e.message}", e)
|
||||
logger.error("执行交易失败: ${e.message}", e)
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Builder Relayer 执行 PROXY(Magic)交易(Gasless)
|
||||
* 参考: builder-relayer-client client.ts executeProxyTransactions, builder/proxy.ts
|
||||
*/
|
||||
private suspend fun executeViaBuilderRelayerProxy(
|
||||
privateKey: String,
|
||||
proxyAddress: String,
|
||||
safeTx: SafeTransaction,
|
||||
builderApiKey: String,
|
||||
builderSecret: String,
|
||||
builderPassphrase: String
|
||||
): Result<String> {
|
||||
val relayerApi = retrofitFactory.createBuilderRelayerApi(
|
||||
relayerUrl = PolymarketConstants.BUILDER_RELAYER_URL,
|
||||
apiKey = builderApiKey,
|
||||
secret = builderSecret,
|
||||
passphrase = builderPassphrase
|
||||
)
|
||||
|
||||
val cleanPrivateKey = privateKey.removePrefix("0x")
|
||||
val privateKeyBigInt = BigInteger(cleanPrivateKey, 16)
|
||||
val credentials = org.web3j.crypto.Credentials.create(privateKeyBigInt.toString(16))
|
||||
val fromAddress = credentials.address
|
||||
|
||||
val relayPayloadResponse = relayerApi.getRelayPayload(fromAddress, "PROXY")
|
||||
if (!relayPayloadResponse.isSuccessful || relayPayloadResponse.body() == null) {
|
||||
val errorBody = relayPayloadResponse.errorBody()?.string() ?: "未知错误"
|
||||
logger.error("获取 Relay Payload 失败: code=${relayPayloadResponse.code()}, body=$errorBody")
|
||||
return Result.failure(Exception("获取 Relay Payload 失败: ${relayPayloadResponse.code()} - $errorBody"))
|
||||
}
|
||||
val relayPayload = relayPayloadResponse.body()!!
|
||||
val relayAddress = relayPayload.address
|
||||
val nonce = relayPayload.nonce
|
||||
|
||||
val proxyCallData = encodeProxyTransactionData(safeTx)
|
||||
val gasLimit = defaultProxyGasLimit
|
||||
|
||||
val structHash = createProxyStructHash(
|
||||
from = fromAddress,
|
||||
to = proxyFactoryAddress,
|
||||
data = proxyCallData,
|
||||
txFee = "0",
|
||||
gasPrice = "0",
|
||||
gasLimit = gasLimit,
|
||||
nonce = nonce,
|
||||
relayHubAddress = relayHubAddress,
|
||||
relayAddress = relayAddress
|
||||
)
|
||||
|
||||
val prefix = "\u0019Ethereum Signed Message:\n32".toByteArray(Charsets.UTF_8)
|
||||
val messageWithPrefix = ByteArray(prefix.size + structHash.size)
|
||||
System.arraycopy(prefix, 0, messageWithPrefix, 0, prefix.size)
|
||||
System.arraycopy(structHash, 0, messageWithPrefix, prefix.size, structHash.size)
|
||||
|
||||
val keccak256 = org.bouncycastle.crypto.digests.KeccakDigest(256)
|
||||
keccak256.update(messageWithPrefix, 0, messageWithPrefix.size)
|
||||
val hashWithPrefix = ByteArray(keccak256.digestSize)
|
||||
keccak256.doFinal(hashWithPrefix, 0)
|
||||
|
||||
val ecKeyPair = org.web3j.crypto.ECKeyPair.create(privateKeyBigInt)
|
||||
val signature = org.web3j.crypto.Sign.signMessage(hashWithPrefix, ecKeyPair, false)
|
||||
val sigHex = "0x" + org.web3j.utils.Numeric.toHexString(signature.r).removePrefix("0x").padStart(64, '0') +
|
||||
org.web3j.utils.Numeric.toHexString(signature.s).removePrefix("0x").padStart(64, '0') +
|
||||
String.format("%02x", (signature.v as ByteArray).getOrElse(0) { 0 }.toInt() and 0xff)
|
||||
|
||||
val request = BuilderRelayerApi.TransactionRequest(
|
||||
type = "PROXY",
|
||||
from = fromAddress,
|
||||
to = proxyFactoryAddress,
|
||||
proxyWallet = proxyAddress,
|
||||
data = proxyCallData,
|
||||
nonce = nonce,
|
||||
signature = sigHex,
|
||||
signatureParams = BuilderRelayerApi.SignatureParams(
|
||||
gasPrice = "0",
|
||||
gasLimit = gasLimit,
|
||||
relayerFee = "0",
|
||||
relayHub = relayHubAddress,
|
||||
relay = relayAddress
|
||||
),
|
||||
metadata = "Redeem positions via Builder Relayer PROXY"
|
||||
)
|
||||
|
||||
val response = relayerApi.submitTransaction(request)
|
||||
if (!response.isSuccessful || response.body() == null) {
|
||||
val errorBody = response.errorBody()?.string() ?: "未知错误"
|
||||
logger.error("Builder Relayer PROXY API 调用失败: code=${response.code()}, body=$errorBody")
|
||||
return Result.failure(Exception("Builder Relayer PROXY 调用失败: ${response.code()} - $errorBody"))
|
||||
}
|
||||
|
||||
val relayerResponse = response.body()!!
|
||||
val txHash = relayerResponse.transactionHash ?: relayerResponse.hash
|
||||
?: return Result.failure(Exception("Builder Relayer 返回的交易哈希为空"))
|
||||
logger.info("Builder Relayer PROXY 执行成功: transactionID=${relayerResponse.transactionID}, txHash=$txHash")
|
||||
return Result.success(txHash)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码 ProxyFactory.proxy(calls) 调用数据
|
||||
* 参考: builder-relayer-client encode/proxy.ts, abis proxyFactory proxy((uint8,address,uint256,bytes)[])
|
||||
*/
|
||||
private fun encodeProxyTransactionData(safeTx: SafeTransaction): String {
|
||||
val selector = EthereumUtils.getFunctionSelector("proxy((uint8,address,uint256,bytes)[])")
|
||||
val callData = safeTx.data.removePrefix("0x")
|
||||
val dataLen = callData.length / 2
|
||||
val dataLenPadded = (dataLen + 31) / 32 * 32 * 2
|
||||
val dataPadded = callData.padEnd(dataLenPadded, '0')
|
||||
|
||||
val arrayOffset = EthereumUtils.encodeUint256(BigInteger.valueOf(32))
|
||||
val arrayLength = EthereumUtils.encodeUint256(BigInteger.ONE)
|
||||
val typeCode = EthereumUtils.encodeUint256(BigInteger.ONE)
|
||||
val toEncoded = EthereumUtils.encodeAddress(safeTx.to)
|
||||
val valueEncoded = EthereumUtils.encodeUint256(BigInteger.ZERO)
|
||||
val dataOffsetInTuple = BigInteger.valueOf(192)
|
||||
val dataOffsetEncoded = EthereumUtils.encodeUint256(dataOffsetInTuple)
|
||||
val dataLengthEncoded = EthereumUtils.encodeUint256(BigInteger.valueOf(dataLen.toLong()))
|
||||
return "0x" + selector.removePrefix("0x") + arrayOffset + arrayLength +
|
||||
typeCode + toEncoded + valueEncoded + dataOffsetEncoded +
|
||||
dataLengthEncoded + dataPadded
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 PROXY 结构哈希,参考 builder-relayer-client builder/proxy.ts createStructHash
|
||||
* concat: "rlx:" + from + to + data + txFee + gasPrice + gasLimit + nonce + relayHub + relay, then keccak256
|
||||
*/
|
||||
private fun createProxyStructHash(
|
||||
from: String,
|
||||
to: String,
|
||||
data: String,
|
||||
txFee: String,
|
||||
gasPrice: String,
|
||||
gasLimit: String,
|
||||
nonce: String,
|
||||
relayHubAddress: String,
|
||||
relayAddress: String
|
||||
): ByteArray {
|
||||
val rlxPrefix = "rlx:".toByteArray(Charsets.UTF_8)
|
||||
val fromBytes = EthereumUtils.hexToBytes(from.lowercase().removePrefix("0x").padStart(40, '0'))
|
||||
val toBytes = EthereumUtils.hexToBytes(to.lowercase().removePrefix("0x").padStart(40, '0'))
|
||||
val dataBytes = EthereumUtils.hexToBytes(data.removePrefix("0x"))
|
||||
val txFeeBytes = EthereumUtils.encodeUint256(BigInteger(txFee)).let { EthereumUtils.hexToBytes(it) }
|
||||
val gasPriceBytes = EthereumUtils.encodeUint256(BigInteger(gasPrice)).let { EthereumUtils.hexToBytes(it) }
|
||||
val gasLimitBytes = EthereumUtils.encodeUint256(BigInteger(gasLimit)).let { EthereumUtils.hexToBytes(it) }
|
||||
val nonceBytes = EthereumUtils.encodeUint256(BigInteger(nonce)).let { EthereumUtils.hexToBytes(it) }
|
||||
val relayHubBytes = EthereumUtils.hexToBytes(relayHubAddress.lowercase().removePrefix("0x").padStart(40, '0'))
|
||||
val relayBytes = EthereumUtils.hexToBytes(relayAddress.lowercase().removePrefix("0x").padStart(40, '0'))
|
||||
|
||||
val concat = rlxPrefix + fromBytes + toBytes + dataBytes + txFeeBytes + gasPriceBytes +
|
||||
gasLimitBytes + nonceBytes + relayHubBytes + relayBytes
|
||||
return EthereumUtils.keccak256(concat)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Builder Relayer 执行交易(Gasless)
|
||||
* 参考: builder-relayer-client/src/client.ts 的 execute 方法
|
||||
|
||||
@@ -227,8 +227,8 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
||||
<Radio value="safe">
|
||||
{t('accountImport.walletTypeSafe')}
|
||||
</Radio>
|
||||
<Radio value="magic" disabled>
|
||||
{t('accountImport.walletTypeMagic')} {t('accountImport.magicNotSupported')}
|
||||
<Radio value="magic">
|
||||
{t('accountImport.walletTypeMagic')}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
"walletTypeHelp": "Web3 Wallet: Polymarket accounts connected via browser wallets like MetaMask\nMagic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.)",
|
||||
"walletTypeMagic": "Magic (Email/Social Login)",
|
||||
"walletTypeSafe": "Web3 Wallet",
|
||||
"magicNotSupported": "(Not Supported)"
|
||||
"magicNotSupported": ""
|
||||
},
|
||||
"leader": {
|
||||
"title": "Leader Management",
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
"walletTypeHelp": "Web3钱包:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户\nMagic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户",
|
||||
"walletTypeMagic": "Magic(邮箱/社交账号登录)",
|
||||
"walletTypeSafe": "Web3钱包",
|
||||
"magicNotSupported": "(暂不支持)"
|
||||
"magicNotSupported": ""
|
||||
},
|
||||
"leader": {
|
||||
"title": "Leader 管理",
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
"walletTypeHelp": "Web3錢包:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶\nMagic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶",
|
||||
"walletTypeMagic": "Magic(郵箱/社群帳號登入)",
|
||||
"walletTypeSafe": "Web3錢包",
|
||||
"magicNotSupported": "(暫不支持)"
|
||||
"magicNotSupported": ""
|
||||
},
|
||||
"leader": {
|
||||
"title": "Leader 管理",
|
||||
|
||||
Reference in New Issue
Block a user