diff --git a/.changeset/plenty-owls-beg.md b/.changeset/plenty-owls-beg.md new file mode 100644 index 0000000..101c623 --- /dev/null +++ b/.changeset/plenty-owls-beg.md @@ -0,0 +1,5 @@ +--- +"@soulwallet/sdk": patch +--- + +New function export: 'async getSemiValidSignature(args...) ` diff --git a/packages/soulwallet-sdk/__tests__/activateWallet.tesdt.ts b/packages/soulwallet-sdk/__tests__/activateWallet.tesdt.ts new file mode 100644 index 0000000..4eb0be3 --- /dev/null +++ b/packages/soulwallet-sdk/__tests__/activateWallet.tesdt.ts @@ -0,0 +1,214 @@ +import { TypedDataDomain, TypedDataField, ethers } from 'ethers'; +import { + SoulWallet, + UserOperation, + PackedUserOperation, + UserOpUtils, + UserOpErrors, + UserOpErrorCodes, + L1KeyStore, + Ok, Err, Result, + UserOpReceipt, + UserOpDetail, + UserOpGas, + Bundler, + Transaction, + KeyStoreInfo, + GuardianSignature, + KeyStoreTypedDataType, + InitialKey, + ECCPoint, + SignkeyType, + P256Lib, + WebAuthN, + Base64Url, + WalletFactory +} from '..'; +import { describe, expect, test } from '@jest/globals'; +import { webcrypto } from 'node:crypto'; +import { Hex } from '../src/tools/hex'; +import { + randomBytes +} from 'crypto'; + +describe('ActivateWallet', () => { + test('Activate', async () => { + const RPC = "https://sepolia.base.org"; + const BundlerRPC = "https://api-dev.soulwallet.io/appapi/bundler/base-sepolia/rpc"; + + const SoulWalletDefaultValidator = '0x82621ac52648b738fEdd381a3678851933505762'; + const SoulwalletInstance = '0x50E964af1Dcf5fA3c6C8Ee5C0A903838E9Aa7aa4'; + const SoulwalletFactory = '0xF78Ae187CED0Ca5Fb98100d3F0EAB7a6461d6fC6'; + const DefaultCallbackHandler = '0x880c6eb80583795625935B08AA28EB37F16732C7'; + const AaveUsdcSaveAutomationBaseSepolia = '0x8107e6c74980Df3fDDc8478F6597670742e4B542'; + + const Web3RPC = new ethers.JsonRpcProvider(RPC); + + const soulWallet = new SoulWallet( + Web3RPC, + BundlerRPC, + SoulwalletFactory, + SoulWalletDefaultValidator, + DefaultCallbackHandler, + undefined, + undefined + ); + + // new EOASigner + const signer = ethers.Wallet.createRandom(); + //const signer = new ethers.Wallet('0x0000000000000000000000000000000000000000000000000000000000000004'); + + const index: number = 0; + const initialKeys: InitialKey[] = [signer.address]; + const initialGuardianHash: string = "0x"; + + const _walletAddress = await soulWallet.calcWalletAddress( + index, + initialKeys, + initialGuardianHash + ); + expect(_walletAddress.isOk()).toBe(true); + const walletAddress = _walletAddress.OK; + console.log('walletAddress', walletAddress); + + const calldata = "0x"; + const _userOp = await soulWallet.createUnsignedDeployWalletUserOp( + index, + initialKeys, + initialGuardianHash, + calldata + ); + + expect(_userOp.isOk()).toBe(true); + const userOp = _userOp.OK; + + { + + // get gas price + const gasPrice = await Web3RPC.getFeeData(); + expect(gasPrice).not.toBeNull(); + expect(gasPrice.maxFeePerGas).not.toBeNull(); + expect(gasPrice.maxPriorityFeePerGas).not.toBeNull(); + userOp.maxFeePerGas = gasPrice.maxFeePerGas!; + userOp.maxPriorityFeePerGas = gasPrice.maxPriorityFeePerGas!; + } + + { + const re = await soulWallet.estimateUserOperationGas(SoulWalletDefaultValidator, userOp, SignkeyType.EOA); + if (userOp.callData === '0x') { + userOp.callGasLimit = 1; + } + expect(re.isOk()).toBe(true); + } + + let usePaymaster = true; + if (usePaymaster) { + const entryPoint = (await soulWallet.entryPoint()).OK; + const chainId = '0x' + ((await Web3RPC.getNetwork()).chainId).toString(16); + userOp.paymaster = ""; + userOp.paymasterVerificationGasLimit = 0; + userOp.paymasterPostOpGasLimit = 0; + userOp.paymasterData = "0x"; + userOp.signature = (await soulWallet.getSemiValidSignature(SoulWalletDefaultValidator, userOp, SignkeyType.EOA)).OK; + // const SponsorRPC = new ethers.JsonRpcProvider('https://api.pimlico.io/v2/base-sepolia/rpc?apikey=58e25ab4-7814-4e56-87bb-af3016dae2df'); + + // try { + // const result = await SponsorRPC.send( + // "pm_sponsorUserOperation", [ + // UserOpUtils.userOperationToJSON(userOp), + // { + // entryPoint: entryPoint + // } + // ]); + // console.log('result', result); + // } catch (e) { + // console.log('error', e); + // } + const sponsorUrl = "https://api-dev.soulwallet.io/appapi/sponsor/sponsor-op"; + const sponsorData = { + chainId: chainId, + entryPoint: entryPoint, + op: JSON.parse(UserOpUtils.userOperationToJSON(userOp)) + }; + const re = await fetch(sponsorUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(sponsorData), + }); + const json = await re.json(); + if ( + typeof json === 'object' && + typeof json.msg === 'string' && + json.msg === "success" && + typeof json.data === 'object' && + typeof json.data.callGasLimit === 'string' && + typeof json.data.paymaster === 'string' && + typeof json.data.paymasterData === 'string' && + typeof json.data.paymasterPostOpGasLimit === 'string' && + typeof json.data.paymasterVerificationGasLimit === 'string' && + typeof json.data.verificationGasLimit === 'string' + ) { + userOp.callGasLimit = BigInt(json.data.callGasLimit); + userOp.paymaster = json.data.paymaster; + userOp.paymasterData = json.data.paymasterData; + userOp.paymasterPostOpGasLimit = BigInt(json.data.paymasterPostOpGasLimit); + userOp.paymasterVerificationGasLimit = BigInt(json.data.paymasterVerificationGasLimit); + userOp.preVerificationGas = BigInt(json.data.preVerificationGas); + userOp.verificationGasLimit = BigInt(json.data.verificationGasLimit); + } else { + throw new Error('sponsor failed'); + } + console.log('json', json); + + } else { + const preFund = await soulWallet.preFund(userOp); + expect(preFund.isOk()).toBe(true); + // get balance + const _balance = await Web3RPC.getBalance(walletAddress); + const missfund = BigInt(preFund.OK.missfund); + expect(_balance >= missfund).toBe(true); + } + + { + const _userOpHash = await soulWallet.userOpHash(userOp); + expect(_userOpHash.isOk()).toBe(true); + const userOpHash = _userOpHash.OK; + const dateNow = Math.floor(new Date().getTime() / 1000); + const _re = await soulWallet.packRawHash(userOpHash, dateNow - 1000 * 60 * 60, dateNow + 1000 * 60 * 60); + expect(_re.isOk()).toBe(true); + const packedHash: string = _re.OK.packedHash; + const validationData: string = _re.OK.validationData; + + const _signature = signer.signMessageSync(ethers.getBytes(packedHash)); + + const signature = await soulWallet.packUserOpEOASignature(SoulWalletDefaultValidator, _signature, validationData); + expect(signature.isOk()).toBe(true); + userOp.signature = signature.OK; + } + { + const packedUserOp = UserOpUtils.packUserOp(userOp); + const packedUserOpJson = UserOpUtils.packedUserOperationToJSON(packedUserOp); + const tupleStr = UserOpUtils.packedUserOperationToTuple(packedUserOp); + console.log('packedUserOp', packedUserOp); + const re = await soulWallet.sendUserOperation(userOp); + if (re.isErr()) { + debugger; + console.log('error', re.ERR.toString()); + } + expect(re.isOk()).toBe(true); + } + + + + + + + + + + + }, 1000 * 60); +}); + diff --git a/packages/soulwallet-sdk/__tests__/main.test.ts b/packages/soulwallet-sdk/__tests__/main.test.ts index ac743a8..e3ba4d2 100644 --- a/packages/soulwallet-sdk/__tests__/main.test.ts +++ b/packages/soulwallet-sdk/__tests__/main.test.ts @@ -328,7 +328,6 @@ describe('SDK', () => { "0x8f63d7dD6A3F5938616Ef06016BBf25BD6023315" ], "0x55e85a731014097612c7d462fbdededcb5f50a5cb64b0c2068cfe017b51268d0"); console.log(a2); - debugger; const userop = await soulwallet.createUnsignedDeployWalletUserOp(0, [ "0x8f63d7dD6A3F5938616Ef06016BBf25BD6023315" ], "0x", undefined, undefined); diff --git a/packages/soulwallet-sdk/src/interface/IUserOpErrors.ts b/packages/soulwallet-sdk/src/interface/IUserOpErrors.ts index 83eefa2..603708d 100644 --- a/packages/soulwallet-sdk/src/interface/IUserOpErrors.ts +++ b/packages/soulwallet-sdk/src/interface/IUserOpErrors.ts @@ -31,7 +31,7 @@ export class UserOpErrors extends Error { this.data = data; } toString(): string { - return `UserOpErrors: ${this.message} (${this.code})`; + return `UserOpErrors - ${this.code}\t${this.message}`; } } diff --git a/packages/soulwallet-sdk/src/soulWallet.ts b/packages/soulwallet-sdk/src/soulWallet.ts index 3e484f1..4f5f0c1 100644 --- a/packages/soulwallet-sdk/src/soulWallet.ts +++ b/packages/soulwallet-sdk/src/soulWallet.ts @@ -547,7 +547,7 @@ export class SoulWallet implements ISoulWallet { ); } - async estimateUserOperationGas(validatorAddress: string, userOp: UserOperation, signkeyType?: SignkeyType, semiValidGuardHookInputData?: GuardHookInputData): Promise> { + async getSemiValidSignature(validatorAddress: string, userOp: UserOperation, signkeyType?: SignkeyType, semiValidGuardHookInputData?: GuardHookInputData): Promise> { if (semiValidGuardHookInputData !== undefined) { if (semiValidGuardHookInputData.sender.toLowerCase() !== userOp.sender.toLowerCase()) { return new Err( @@ -560,6 +560,55 @@ export class SoulWallet implements ISoulWallet { ); } } + // semiValidSignature + const validationData = (BigInt(68719476735) << BigInt(160)) + (BigInt(1599999999) << BigInt(160 + 48)); + let signatureRet: Result; + if (signkeyType === SignkeyType.P256) { + signatureRet = await this.packUserOpP256Signature( + validatorAddress, + { + messageHash: "0x83714056da6e6910b51595330c2c2cdfbf718f2deff5bdd84b95df7a7f36f6dd", + publicKey: { + x: "0xe89e8b4be943fadb4dc599fe2e8af87a79b438adde328a3b72d43324506cd5b6", + y: "0x4fbfe4a2f9934783c3b1af712ee87abc08f576e79346efc3b8355d931bd7b976" + }, + r: "0x2ae3ddfe4cc414dc0fad7ff3a5c960d1cee1211722d3099ade76e5ac1826731a", + s: "0x87e5d654f357e4cd6cb52512b2da4d91eae0ae48e9d892ce532b9352f63a55d6", + authenticatorData: "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000", + clientDataSuffix: "\",\"origin\":\"http://localhost:5500\",\"crossOrigin\":false}" + }, + `0x${validationData.toString(16)}`, + semiValidGuardHookInputData + ); + } else if (signkeyType === SignkeyType.RS256) { + signatureRet = await this.packUserOpRS256Signature( + validatorAddress, + { + messageHash: "0x83714056da6e6910b51595330c2c2cdfbf718f2deff5bdd84b95df7a7f36f6dd", + publicKey: { + e: "0x010001", + n: "0xc6807412ed8d616565508c879292686bb1db6f0561b62c7b66a2d3806d161c0ccef888d2c1efcf061e268a15e61e7d023646014c33b1ead31bef0e5379558e6ff71249b143c03abec33a2b055fc8e0a947393512e7e26ad33f0ad4aabfe32d0642965856d8e20204a44d78e36cc90db2a12cfbc37fa97360efd3a735c625ab814d6f6bb7c63abe261bbd9c52681c6221f936d617dc84de61556074f6c1d73b3ffd242d2940d3c02c5a269e390bd8e6b6301a5a0a339910f6480403d27d32c2ff2b9bf33bae45c36f423025ca41f05c97be5148b2cb276b31441274100bf3ca0b50da1ee04511be9bdbb4f12b7579ab3da780bc2c615e2a49f5e1f750b034d0af" + }, + signature: "0x357a51b26e22dcfb87346bb6938cfb2b066d48d4c36cafd30ac105fe345199966f24c87fa66791d4c2341b97fa07421ef4115a9923e6249c53887b6f2313df60654083758fe7104286490e1a37481246395dcb097a86645dc3251afa5c87e4bc8f2960cfe3efa34c44bbee0fe3d602866c81a5fc432709443c623595556670a427502c63c1e6a86761c8b326b5f503bdcfdcf1f00871f330a9fddf6ae11adcff4a5f411edec30019c86936f8064b70f88cdb56ba6635175f7ef5c74f52de9db5498e4c4d4b75c8a3210e5b1a631af271c4b613a8752b2a1cea499bd81115d9ed34305d9ab4af753dc9b9630478fdb0787e5f5e0efb76504d15eff5fd02a38bf1", + authenticatorData: "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + clientDataSuffix: "\",\"origin\":\"http://localhost:5500\",\"crossOrigin\":false}" + }, + `0x${validationData.toString(16)}`, + semiValidGuardHookInputData + ); + } else { + const signature = "0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c"; + signatureRet = await this.packUserOpEOASignature(validatorAddress, signature, `0x${validationData.toString(16)}`, semiValidGuardHookInputData); + } + if (signatureRet.isErr() === true) { + return new Err( + new UserOpErrors(UserOpErrorCodes.UnknownError, signatureRet.ERR.message) + ); + } + return new Ok(signatureRet.OK); + } + + async estimateUserOperationGas(validatorAddress: string, userOp: UserOperation, signkeyType?: SignkeyType, semiValidGuardHookInputData?: GuardHookInputData): Promise> { const semiValidSignature = userOp.signature === "0x"; const _onChainConfig = await this.getOnChainConfig(); if (_onChainConfig.isErr() === true) { @@ -567,52 +616,11 @@ export class SoulWallet implements ISoulWallet { } try { if (semiValidSignature) { - // semiValidSignature - const validationData = (BigInt(68719476735) << BigInt(160)) + (BigInt(1599999999) << BigInt(160 + 48)); - let signatureRet: Result; - if (signkeyType === SignkeyType.P256) { - signatureRet = await this.packUserOpP256Signature( - validatorAddress, - { - messageHash: "0x83714056da6e6910b51595330c2c2cdfbf718f2deff5bdd84b95df7a7f36f6dd", - publicKey: { - x: "0xe89e8b4be943fadb4dc599fe2e8af87a79b438adde328a3b72d43324506cd5b6", - y: "0x4fbfe4a2f9934783c3b1af712ee87abc08f576e79346efc3b8355d931bd7b976" - }, - r: "0x2ae3ddfe4cc414dc0fad7ff3a5c960d1cee1211722d3099ade76e5ac1826731a", - s: "0x87e5d654f357e4cd6cb52512b2da4d91eae0ae48e9d892ce532b9352f63a55d6", - authenticatorData: "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000", - clientDataSuffix: "\",\"origin\":\"http://localhost:5500\",\"crossOrigin\":false}" - }, - `0x${validationData.toString(16)}`, - semiValidGuardHookInputData - ); - } else if (signkeyType === SignkeyType.RS256) { - signatureRet = await this.packUserOpRS256Signature( - validatorAddress, - { - messageHash: "0x83714056da6e6910b51595330c2c2cdfbf718f2deff5bdd84b95df7a7f36f6dd", - publicKey: { - e: "0x010001", - n: "0xc6807412ed8d616565508c879292686bb1db6f0561b62c7b66a2d3806d161c0ccef888d2c1efcf061e268a15e61e7d023646014c33b1ead31bef0e5379558e6ff71249b143c03abec33a2b055fc8e0a947393512e7e26ad33f0ad4aabfe32d0642965856d8e20204a44d78e36cc90db2a12cfbc37fa97360efd3a735c625ab814d6f6bb7c63abe261bbd9c52681c6221f936d617dc84de61556074f6c1d73b3ffd242d2940d3c02c5a269e390bd8e6b6301a5a0a339910f6480403d27d32c2ff2b9bf33bae45c36f423025ca41f05c97be5148b2cb276b31441274100bf3ca0b50da1ee04511be9bdbb4f12b7579ab3da780bc2c615e2a49f5e1f750b034d0af" - }, - signature: "0x357a51b26e22dcfb87346bb6938cfb2b066d48d4c36cafd30ac105fe345199966f24c87fa66791d4c2341b97fa07421ef4115a9923e6249c53887b6f2313df60654083758fe7104286490e1a37481246395dcb097a86645dc3251afa5c87e4bc8f2960cfe3efa34c44bbee0fe3d602866c81a5fc432709443c623595556670a427502c63c1e6a86761c8b326b5f503bdcfdcf1f00871f330a9fddf6ae11adcff4a5f411edec30019c86936f8064b70f88cdb56ba6635175f7ef5c74f52de9db5498e4c4d4b75c8a3210e5b1a631af271c4b613a8752b2a1cea499bd81115d9ed34305d9ab4af753dc9b9630478fdb0787e5f5e0efb76504d15eff5fd02a38bf1", - authenticatorData: "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", - clientDataSuffix: "\",\"origin\":\"http://localhost:5500\",\"crossOrigin\":false}" - }, - `0x${validationData.toString(16)}`, - semiValidGuardHookInputData - ); - } else { - const signature = "0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c"; - signatureRet = await this.packUserOpEOASignature(validatorAddress, signature, `0x${validationData.toString(16)}`, semiValidGuardHookInputData); - } - if (signatureRet.isErr() === true) { - return new Err( - new UserOpErrors(UserOpErrorCodes.UnknownError, signatureRet.ERR.message) - ); + const re = await this.getSemiValidSignature(validatorAddress, userOp, signkeyType, semiValidGuardHookInputData); + if (re.isErr() === true) { + return new Err(re.ERR); } - userOp.signature = signatureRet.OK; + userOp.signature = re.OK; } const userOpGasRet = await this.Bundler.eth_estimateUserOperationGas(_onChainConfig.OK.entryPoint, userOp); if (userOpGasRet.isErr() === true) { diff --git a/packages/soulwallet-sdk/src/tools/convert.ts b/packages/soulwallet-sdk/src/tools/convert.ts index 368f18d..9f2f5c2 100644 --- a/packages/soulwallet-sdk/src/tools/convert.ts +++ b/packages/soulwallet-sdk/src/tools/convert.ts @@ -24,6 +24,37 @@ function _HexstringToBytes(value: string): string { } } +function packedUserOperationToTuple(packedUserOp: PackedUserOperation): string { + let tupleStr = '['; + tupleStr += `"${ethers.getAddress(packedUserOp.sender)}",` + tupleStr += `${BigInt(packedUserOp.nonce).toString(10)},` + tupleStr += `"${_HexstringToBytes(packedUserOp.initCode)}",` + tupleStr += `"${_HexstringToBytes(packedUserOp.callData)}",` + tupleStr += `"${_HexstringToBytes(packedUserOp.accountGasLimits)}",` + tupleStr += `${BigInt(packedUserOp.preVerificationGas).toString(10)},` + tupleStr += `"${_HexstringToBytes(packedUserOp.gasFees)}",` + tupleStr += `"${_HexstringToBytes(packedUserOp.paymasterAndData)}",` + tupleStr += `"${_HexstringToBytes(packedUserOp.signature)}"` + tupleStr += ']'; + + return tupleStr.toLowerCase(); +} + +function packedUserOperationToJSON(packedUserOp: PackedUserOperation): string { + const obj = { + sender: ethers.getAddress(packedUserOp.sender), + nonce: _BigNumberishToHexString(packedUserOp.nonce), + initCode: _HexstringToBytes(packedUserOp.initCode), + callData: _HexstringToBytes(packedUserOp.callData), + accountGasLimits: _BigNumberishToHexString(packedUserOp.accountGasLimits), + preVerificationGas: _BigNumberishToHexString(packedUserOp.preVerificationGas), + gasFees: _BigNumberishToHexString(packedUserOp.gasFees), + paymasterAndData: _HexstringToBytes(packedUserOp.paymasterAndData), + signature: _HexstringToBytes(packedUserOp.signature) + }; + return JSON.stringify(obj); +} + function userOperationToJSON(userOp: UserOperation): string { let factory: string | null = null; if (userOp.factory !== null && userOp.factory.length === 42 && userOp.factory !== ethers.ZeroAddress) { @@ -238,9 +269,11 @@ function unpackUserOp(packedUserOp: PackedUserOperation): UserOperation { } export { + packedUserOperationToJSON, userOperationFromJSON, userOperationToJSON, bigIntToNumber, packUserOp, - unpackUserOp + unpackUserOp, + packedUserOperationToTuple } diff --git a/packages/soulwallet-sdk/src/userOpUtils.ts b/packages/soulwallet-sdk/src/userOpUtils.ts index d7c915b..116b6fa 100644 --- a/packages/soulwallet-sdk/src/userOpUtils.ts +++ b/packages/soulwallet-sdk/src/userOpUtils.ts @@ -1,5 +1,5 @@ import { PackedUserOperation, UserOperation } from './interface/UserOperation.js'; -import { userOperationFromJSON, userOperationToJSON, packUserOp, unpackUserOp } from './tools/convert.js'; +import { packedUserOperationToTuple, packedUserOperationToJSON, userOperationFromJSON, userOperationToJSON, packUserOp, unpackUserOp } from './tools/convert.js'; import { getUserOpHash } from './tools/userOpHash.js' /** @@ -10,6 +10,24 @@ import { getUserOpHash } from './tools/userOpHash.js' */ export class UserOpUtils { + static packedUserOperationToTuple(packedUserOp: PackedUserOperation): string { + return packedUserOperationToTuple(packedUserOp); + } + + /** + * packedUserOperationToJSON + * + * @static + * @param {PackedUserOperation} packedUserOp + * @return {*} {string} + * @memberof UserOpUtils + */ + static packedUserOperationToJSON(packedUserOp: PackedUserOperation): string { + return packedUserOperationToJSON(packedUserOp); + } + + + /** * userOperationToJSON *