diff --git a/.gitignore b/.gitignore index 6bad88c..d8d3e42 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules /.idea/ /packages/sdk/dist/ /packages/accounts/dist/ +/yarn-error.log diff --git a/packages/accounts/package.json b/packages/accounts/package.json index b738a39..e71b9f1 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -6,6 +6,9 @@ "clean": "rm -rf dist" }, "version": "0.1.0", - "main": "index.js", - "license": "MIT" + "main": "./dist/index.js", + "license": "MIT", + "dependencies": { + "permissionless": "^0.3.2" + } } diff --git a/packages/accounts/src/MultiChainSmartAccount.ts b/packages/accounts/src/MultiChainSmartAccount.ts new file mode 100644 index 0000000..29b8fa5 --- /dev/null +++ b/packages/accounts/src/MultiChainSmartAccount.ts @@ -0,0 +1,117 @@ +import { SmartAccount } from 'viem/account-abstraction' +import { Call, Hex, LocalAccount, } from 'viem' +import { + asCall, + BaseMultichainSmartAccount, + FunctionCall, + ICrossChainSdk, + MultichainBundlerManager, + UserOperation +} from '@eil-protocol/sdk' +import { toSimpleSmartAccount } from 'permissionless/accounts' + +/** + * a simple MultichainAccount. using SimpleSmartAccount on each chain. + * Note that the signature method called for each SimpleSmartAccount instead separately. + * Also, its encoding can't support dynamic calls. + */ +export class MultiChainSmartAccount extends BaseMultichainSmartAccount { + + accounts: Map = new Map() + + /** + * Creates a new MultiChainSmartAccount instance + * @param owner - The account owner used for multichain signing. Must belong to the same owner across all chains. + * @param sdk - the sdk configuration to read supported network + * @param accounts - Array of {@link SmartAccount} instances for different chains (optional). + * @notice The owner must have signing capabilities for all chains. + * @dev Note that SimpleAccount API doesn't expose the owner directly. + */ + protected constructor ( + readonly owner: LocalAccount, + sdk: ICrossChainSdk, + accounts: SmartAccount[] + ) { + + const bundlerManager = new MultichainBundlerManager(sdk.getNetworkEnv().input.chainInfos) + super(bundlerManager) + this.addAccounts(accounts) + } + + static async create ( + owner: LocalAccount, + sdk: ICrossChainSdk, + accounts?: SmartAccount[] + ): Promise { + const networkEnv = sdk.getNetworkEnv() + if (accounts == null) { + accounts = [] + for (const chain of networkEnv.input.chainInfos) { + let client = networkEnv.chains.clientOn(chain.chainId) + const entryPointAddress = networkEnv.entrypoints.addressOn(chain.chainId) + + const factoryAddress = '0x2862B77afcF4405e766328E697E0236b9974b8fa' //ep9 simpleAccount factory + const account: SmartAccount = await toSimpleSmartAccount({ + owner, + client, + entryPoint: { + address: entryPointAddress, + version: '0.8' + }, + factoryAddress + }) + accounts.push(account) + } + } + return new MultiChainSmartAccount( + owner, + sdk, + accounts + ) + } + + //add chain-specific instances of the account. + //TODO: currently needs viem "SmartAccount" for each chain. better unify it + // (some methods are chain-specific, like "getNonce". others (like encode) are not. + protected addAccounts (accounts: SmartAccount[]) { + for (const account of accounts) { + const chainId = BigInt(account.client.chain?.id!) + if (this.accounts.has(chainId)) { + throw new Error(`Account object already exists for chainId: ${chainId}`) + } + this.accounts.set(chainId, account) + } + } + + hasAddress (chainId: bigint): boolean { + return this.accounts.has(chainId) + } + + contractOn (chainId: bigint): SmartAccount { + if (!this.accounts.has(chainId)) { + throw new Error(`No account found for chainId: ${chainId}`) + } + return this.accounts.get(chainId)! + } + + async signUserOps (userOps: UserOperation[]): Promise { + // naive implementation: sign using each per-chain account. + const result: UserOperation[] = [] + for (const userOp of userOps) { + const chainId = userOp.chainId + if (chainId == null) { + throw new Error(`signUserOps: can only sign userOps with chain`) + } + const account = this.contractOn(chainId) + const signature = await account.signUserOperation(userOp as any) + result.push({ ...userOp, signature }) + } + return result + } + + async encodeCalls (chainId: bigint, calls: Array): Promise { + const plainCalls: Call[] = calls.map(call => asCall(chainId, call)) + return this.contractOn(chainId).encodeCalls(plainCalls) + } +} + diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index 5ac8a03..2559b60 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -1 +1,2 @@ -console.log('accounts') +export * from './ambire/AmbireMultiChainSmartAccount.js' +export * from './MultiChainSmartAccount.js' diff --git a/packages/sdk/src/sdk/CrossChainSdk.ts b/packages/sdk/src/sdk/CrossChainSdk.ts index 14bd43c..62b936b 100644 --- a/packages/sdk/src/sdk/CrossChainSdk.ts +++ b/packages/sdk/src/sdk/CrossChainSdk.ts @@ -1,7 +1,6 @@ -import { CrossChainConfig } from './config/index.js' -import { ICrossChainBuilder, ICrossChainSdk, IJsonRpcProvider } from './types/index.js' -import { CrossChainBuilder, InternalConfig } from './builder/index.js' -import { IMultiChainSmartAccount } from './account/index.js' +import { CrossChainConfig, defaultCrossChainConfig } from './config/index.js' +import { AddressPerChain, ICrossChainBuilder, ICrossChainSdk, MultichainToken } from './types/index.js' +import { CrossChainBuilder, NetworkEnvironment } from './builder/index.js' /** * This class is the main component for building cross-chain actions. @@ -9,21 +8,30 @@ import { IMultiChainSmartAccount } from './account/index.js' */ export class CrossChainSdk implements ICrossChainSdk { - config: InternalConfig + networkEnv: NetworkEnvironment constructor ( - readonly account: IMultiChainSmartAccount, - config: CrossChainConfig, - readonly walletProvider: IJsonRpcProvider | undefined = undefined + config: CrossChainConfig = defaultCrossChainConfig ) { - this.config = new InternalConfig(config) + this.networkEnv = new NetworkEnvironment(config) } /** * create a builder for a cross-chain operation */ createBuilder (): ICrossChainBuilder { - return new CrossChainBuilder(this.config, this.account) + return new CrossChainBuilder(this.networkEnv) + } + + /** + * create a MultichainToken with the given deployment addresses + */ + createToken (name: string, deployments: AddressPerChain): MultichainToken { + return new MultichainToken(name, this.networkEnv.chains, deployments) + } + + getNetworkEnv (): NetworkEnvironment { + return this.networkEnv } } diff --git a/packages/sdk/src/sdk/actions/VoucherRequestAction.ts b/packages/sdk/src/sdk/actions/VoucherRequestAction.ts index ed39586..b8afb45 100644 --- a/packages/sdk/src/sdk/actions/VoucherRequestAction.ts +++ b/packages/sdk/src/sdk/actions/VoucherRequestAction.ts @@ -1,7 +1,7 @@ import { Address, Call } from 'viem' import { BaseAction, BatchBuilder, FunctionCall, SdkVoucherRequest, toAddress } from '../index.js' -import { NATIVE_ETH } from '../types/Constants.js' +import { NATIVE_ETH } from '../types/index.js' /** * The internal class defining an action to lock the user deposit for the specified {@link SdkVoucherRequest}. diff --git a/packages/sdk/src/sdk/builder/BatchBuilder.ts b/packages/sdk/src/sdk/builder/BatchBuilder.ts index cc5586c..5553689 100644 --- a/packages/sdk/src/sdk/builder/BatchBuilder.ts +++ b/packages/sdk/src/sdk/builder/BatchBuilder.ts @@ -1,9 +1,10 @@ -import { Address, Hex, PrivateKeyAccount, publicActions } from 'viem' +import { Address, Hex, hexToBigInt, PrivateKeyAccount, publicActions } from 'viem' import { BaseAction, FunctionCallAction, prepareCallWithRuntimeVars, VoucherRequestAction } from '../actions/index.js' import { CrossChainBuilder } from './CrossChainBuilder.js' import { CrossChainVoucherCoordinator } from './CrossChainVoucherCoordinator.js' -import { InternalConfig } from './InternalConfig.js' +import { NetworkEnvironment } from './NetworkEnvironment.js' import { + ICrossChainBuilder, InternalVoucherInfo, isCall, isValidAddress, @@ -15,9 +16,8 @@ import { } from '../types/index.js' import { appendPaymasterSignature, getUserOpHash } from '../index.js' import { assert } from '../sdkUtils/SdkUtils.js' -import { IMultiChainSmartAccount } from '../account/index.js' import { Asset } from '../../contractTypes/Asset.js' -import { abiEncodePaymasterData } from '../../utils/index.js' +import { abiEncodePaymasterData, nowSeconds } from '../../utils/index.js' /** * Return the minimum amount for an asset. @@ -56,14 +56,18 @@ export class BatchBuilder { private _vars: Set = new Set() constructor ( + private readonly parentBuilder: ICrossChainBuilder, private readonly ephemeralSigner: PrivateKeyAccount, private readonly coordinator: CrossChainVoucherCoordinator, - readonly config: InternalConfig, - private readonly smartAccount: IMultiChainSmartAccount, + readonly config: NetworkEnvironment, readonly paymaster: `0x${string}`, readonly chainId: bigint ) {} + endBatch (): ICrossChainBuilder { + return this.parentBuilder + } + /** * Add a new dynamic runtime variable to the batch. * Variables can be used to store values that are not known at the time of batch creation. @@ -127,7 +131,7 @@ export class BatchBuilder { } else { assert(req.sourceChainId == this.chainId, `Voucher request sourceChainId ${req.sourceChainId} does not match batch chainId ${this.chainId}`) } - assert(!this.coordinator.has(req), `Voucher request ${req} already exists in this BatchBuilder`) + assert(!this.coordinator.has(req), `Voucher request ${req.ref} already exists in this BatchBuilder`) req.tokens.forEach(token => { if (!isValidAddress(req.sourceChainId!, token.token)) { @@ -138,7 +142,7 @@ export class BatchBuilder { } }) - this.coordinator.set(req, { + this.coordinator.set({ voucher: req, sourceBatch: this, }) @@ -155,13 +159,14 @@ export class BatchBuilder { /** * Use the {@link SdkVoucherRequest} created in an earlier batch to move tokens to this chain. */ - useVoucher (voucher: SdkVoucherRequest): this { + useVoucher (refId: string): this { this.assertNotBuilt() - const internalVoucherInfo = this.coordinator.getVoucherInternalInfo(voucher) - assert(internalVoucherInfo != null, `Voucher request ${voucher} not found in action builder`) + const internalVoucherInfo = this.coordinator.getVoucherInternalInfo(refId) + const voucher = internalVoucherInfo?.voucher! + assert(internalVoucherInfo != null, `Voucher request ${refId} not found in action builder`) assert(this.userOpOverrides?.paymaster == null && this.userOpOverrides?.paymasterData == null, `Cannot override paymaster or paymasterData in a batch that uses vouchers.`) - assert(internalVoucherInfo.destBatch == undefined, `Voucher request ${voucher} already used`) + assert(internalVoucherInfo.destBatch == undefined, `Voucher request ${refId} already used`) assert(voucher.destinationChainId == this.chainId, `Voucher request is for chain ${voucher.destinationChainId}, but batch is for chain ${this.chainId}`) internalVoucherInfo.destBatch = this @@ -179,7 +184,7 @@ export class BatchBuilder { for (const v of allVoucherRequests) { if (v.destinationChainId === this.chainId) { added = true - this.useVoucher(v) + this.useVoucher(v.ref) } } assert(added, `No voucher requests found for chain ${this.chainId}`) @@ -194,7 +199,8 @@ export class BatchBuilder { async createUserOp (): Promise { const chainId = this.chainId - const smartAccount = this.smartAccount.contractOn(chainId) + const mcAccount = this.parentBuilder.getAccount() + const smartAccount = mcAccount.contractOn(chainId) const allCalls = await Promise.all(this.actions.map((action) => { return action.encodeCall(this) })) @@ -209,14 +215,15 @@ export class BatchBuilder { smartAccount.getAddress(), smartAccount.getNonce(), smartAccount.getFactoryArgs(), - calls.length == 0 ? '0x' : this.smartAccount.encodeCalls(chainId, calls) + calls.length == 0 ? '0x' : mcAccount.encodeCalls(chainId, calls) ]) + const nonce1 = BigInt(nowSeconds()) << 64n const { maxFeePerGas, maxPriorityFeePerGas } = await smartAccount.client.extend(publicActions).estimateFeesPerGas() let userOp = { chainId, sender, - nonce, + nonce: nonce1, factory, factoryData, callData, @@ -312,7 +319,7 @@ export class BatchBuilder { } getVoucherInternalInfo (voucher: SdkVoucherRequest): InternalVoucherInfo | undefined { - return this.coordinator.getVoucherInternalInfo(voucher) + return this.coordinator.getVoucherInternalInfo(voucher.ref) } getOutVoucherRequests (): SdkVoucherRequest[] { diff --git a/packages/sdk/src/sdk/builder/CrossChainBuilder.ts b/packages/sdk/src/sdk/builder/CrossChainBuilder.ts index e3514d3..ec7cf14 100644 --- a/packages/sdk/src/sdk/builder/CrossChainBuilder.ts +++ b/packages/sdk/src/sdk/builder/CrossChainBuilder.ts @@ -15,7 +15,7 @@ import IEntryPointInterface from '@account-abstraction/contracts/artifacts/IEntr import { AtomicSwapFeeRule } from '../../contractTypes/AtomicSwapFeeRule.js' import { amountOrMinAmount, BatchBuilder } from './BatchBuilder.js' import { CrossChainExecutor } from './CrossChainExecutor.js' -import { InternalConfig } from './InternalConfig.js' +import { NetworkEnvironment } from './NetworkEnvironment.js' import { ICrossChainBuilder, InternalVoucherInfo, @@ -55,10 +55,10 @@ export class CrossChainBuilder implements ICrossChainBuilder { private initialized = false batchBuilders: BatchBuilder[] = [] + smartAccount: IMultiChainSmartAccount | undefined constructor ( - readonly config: InternalConfig, - readonly smartAccount: IMultiChainSmartAccount, + readonly config: NetworkEnvironment, ) { this.ephemeralSigner = privateKeyToAccount(generatePrivateKey()) this.coordinator = new CrossChainVoucherCoordinator() @@ -66,17 +66,32 @@ export class CrossChainBuilder implements ICrossChainBuilder { this.feeConfig = { ...defaultFeeConfig, ...config.input.feeConfig ?? {} } } + useAccount (account: IMultiChainSmartAccount): this { + if (this.smartAccount != null) { + throw new Error('cannoot call useAccount() more than once') + } + this.smartAccount = account + return this + } + + getAccount (): IMultiChainSmartAccount { + if (this.smartAccount == null) { + throw new Error('must call useAccount() before build') + } + return this.smartAccount + } + /** * create a new batch, to be executed on the given chain. - * @param chainId + * @param chainId the chain this batch will be executed on. */ - createBatch (chainId: bigint): BatchBuilder { + startBatch (chainId: bigint): BatchBuilder { this.assertNotBuilt() const batch = new BatchBuilder( + this, this.ephemeralSigner, this.coordinator, this.config, - this.smartAccount, this.config.paymasters.addressOn(chainId), chainId ) @@ -145,10 +160,12 @@ export class CrossChainBuilder implements ICrossChainBuilder { entryPointAddress: this.config.entrypoints.addressOn(batch.chainId) } }) + let smartAccount = this.getAccount() for (const userOp of userOpsToSign) { - await this.smartAccount.verifyBundlerConfig(userOp.chainId!, userOp.entryPointAddress!) + + await smartAccount.verifyBundlerConfig(userOp.chainId!, userOp.entryPointAddress!) } - const signedUserOps = await this.smartAccount.signUserOps(userOpsToSign) + const signedUserOps = await smartAccount.signUserOps(userOpsToSign) //update the signed users in the batches batches.forEach((batch, index) => { @@ -228,8 +245,9 @@ export class CrossChainBuilder implements ICrossChainBuilder { * Call the paymaster's 'getSenderNonce' view function for the account. */ async _getVoucherSenderNonce (chainId: bigint) { + let smartAccount = this.getAccount() return await this.config.paymasters.call(chainId, - 'getSenderNonce', [this.smartAccount.addressOn(chainId)]) + 'getSenderNonce', [smartAccount.addressOn(chainId)]) } /** @@ -309,7 +327,7 @@ export class CrossChainBuilder implements ICrossChainBuilder { } getVoucherInternalInfo (voucher: SdkVoucherRequest): InternalVoucherInfo { - const info = this.coordinator.getVoucherInternalInfo(voucher) + const info = this.coordinator.getVoucherInternalInfo(voucher.ref) if (info == null) { throw new Error(`Voucher request ${voucher} not found in action builder`) } @@ -321,8 +339,8 @@ export class CrossChainBuilder implements ICrossChainBuilder { const chainId = voucherInternalInfo.sourceBatch.chainId const allowedXlps = voucherInternalInfo.allowedXlps! const destChainId = voucherRequest.destinationChainId - const account = this.smartAccount.contractOn(chainId) - const destAccount = this.smartAccount.contractOn(destChainId) + const account = this.getAccount().contractOn(chainId) + const destAccount = this.getAccount().contractOn(destChainId) const paymaster = this.config.paymasters.addressOn(chainId) const destPaymaster = this.config.paymasters.addressOn(destChainId) return { diff --git a/packages/sdk/src/sdk/builder/CrossChainExecutor.ts b/packages/sdk/src/sdk/builder/CrossChainExecutor.ts index b1f8180..c483aa1 100644 --- a/packages/sdk/src/sdk/builder/CrossChainExecutor.ts +++ b/packages/sdk/src/sdk/builder/CrossChainExecutor.ts @@ -9,7 +9,7 @@ import { UserOperation } from '../types/index.js' import { CrossChainBuilder } from './CrossChainBuilder.js' -import { InternalConfig } from './InternalConfig.js' +import { NetworkEnvironment } from './NetworkEnvironment.js' import { SessionData } from '../../contractTypes/SessionData.js' import { abiEncodeVouchers, @@ -101,7 +101,7 @@ export class CrossChainExecutor { constructor ( readonly builder: CrossChainBuilder, - readonly config: InternalConfig, + readonly config: NetworkEnvironment, private readonly ephemeralSigner: PrivateKeyAccount, readonly batches: SingleChainBatch[], readonly timeoutSeconds = config.input.execTimeoutSeconds ?? 30, @@ -197,9 +197,7 @@ export class CrossChainExecutor { const { userOp } = batchStatusInfo.batch this.watchForUserOperationEvents(batchStatusInfo, callback) - console.warn('sendUserOperation:') - console.warn(userOp) - this.builder.smartAccount.sendUserOperation(userOp as UserOperation) + this.builder.getAccount().sendUserOperation(userOp as UserOperation) .catch(e => { console.error('Error executing UserOperation:', e) // Validation failure during sending. @@ -299,7 +297,7 @@ export class CrossChainExecutor { console.log(`watching VoucherIssued for ${sender}/${senderNonce} on chain ${chainId}`) const paymaster = this.config.paymasters.addressOn(chainId) const eventPoller = new EventsPoller({ - client: this.config.chains.on(chainId), + client: this.config.chains.clientOn(chainId), abi: this.config.paymasters.abi, eventNames: ['VoucherIssued'], onLog: (log: any) => { @@ -330,7 +328,7 @@ export class CrossChainExecutor { const VoucherRequestCreated = 'VoucherRequestCreated' const eventPoller: IEventPoller = new EventsPoller({ - client: this.config.chains.on(chainId), + client: this.config.chains.clientOn(chainId), abi: [ ...this.config.entrypoints.abi, ...this.config.paymasters.abi diff --git a/packages/sdk/src/sdk/builder/CrossChainVoucherCoordinator.ts b/packages/sdk/src/sdk/builder/CrossChainVoucherCoordinator.ts index b7b8f7e..5247f95 100644 --- a/packages/sdk/src/sdk/builder/CrossChainVoucherCoordinator.ts +++ b/packages/sdk/src/sdk/builder/CrossChainVoucherCoordinator.ts @@ -6,32 +6,32 @@ export class CrossChainVoucherCoordinator { /** * A mapping from the inputs of the Builder to the full voucher requests information for all batches. */ - private vouchersInternalInfo: Map = new Map() + private vouchersInternalInfo: Map = new Map() getAllVoucherInternalInfos (): InternalVoucherInfo[] { return [...this.vouchersInternalInfo.values()] } - getVoucherInternalInfo (sdkVoucherRequest: SdkVoucherRequest): InternalVoucherInfo | undefined { - return this.vouchersInternalInfo.get(sdkVoucherRequest) + getVoucherInternalInfo (refId: string): InternalVoucherInfo | undefined { + return this.vouchersInternalInfo.get(refId) } getAllOutVoucherRequests (): SdkVoucherRequest[] { - return Array.from(this.vouchersInternalInfo.keys()) + return Array.from(this.vouchersInternalInfo.values()).map(info => info.voucher) } has (sdkVoucherRequest: SdkVoucherRequest): boolean { - return this.vouchersInternalInfo.has(sdkVoucherRequest) + return this.vouchersInternalInfo.has(sdkVoucherRequest.ref) } - set (sdkVoucherRequest: SdkVoucherRequest, internalVoucherInfo: InternalVoucherInfo): void { - this.vouchersInternalInfo.set(sdkVoucherRequest, internalVoucherInfo) + set (internalVoucherInfo: InternalVoucherInfo): void { + this.vouchersInternalInfo.set(internalVoucherInfo.voucher.ref, internalVoucherInfo) } updateVoucherXlps (voucherReq: SdkVoucherRequest, xlps: Address[]) { - const info = this.vouchersInternalInfo.get(voucherReq) + const info = this.vouchersInternalInfo.get(voucherReq.ref) if (!info) { - throw new Error(`Voucher request ${voucherReq} not found in action builder`) + throw new Error(`Voucher request ${voucherReq.ref} not found in action builder`) } info.allowedXlps = xlps } diff --git a/packages/sdk/src/sdk/builder/MultichainBundlerManager.ts b/packages/sdk/src/sdk/builder/MultichainBundlerManager.ts index 1bbebec..b4a0e4b 100644 --- a/packages/sdk/src/sdk/builder/MultichainBundlerManager.ts +++ b/packages/sdk/src/sdk/builder/MultichainBundlerManager.ts @@ -1,5 +1,4 @@ import { Address, createClient, fromHex, Hex, http } from 'viem' -import { entryPoint08Address } from 'viem/account-abstraction' import { stringifyBigIntReplacer } from '../sdkUtils/SdkUtils.js' import { IBundlerManager } from '../types/IBundlerManager.js' @@ -8,31 +7,16 @@ import { UserOperation } from '../types/UserOperation.js' import { IJsonRpcProvider } from '../types/index.js' export class MultichainBundlerManager implements IBundlerManager { - isInitialized = false - constructor (readonly chainInfos: ChainInfo[], bundlers: Array<[chainId: bigint, url: string, entryPoint?: Address]> = []) { - for (const [chainId, url, entryPointAddress] of bundlers) { + constructor (readonly chainInfos: ChainInfo[]) { + for (const { chainId, bundlerUrl, entryPointAddress } of chainInfos) { let provider = createClient({ - transport: http(url, { retryCount: 0 }) + transport: http(bundlerUrl, { retryCount: 0 }) }).transport - this.addBundler(chainId, provider, entryPointAddress ?? entryPoint08Address) - } - } - - async initialize (): Promise { - for (const chain of this.chainInfos) { - let provider: IJsonRpcProvider - if (chain.bundlerUrl != null) { - provider = createClient({ - transport: http(chain.bundlerUrl, { retryCount: 0 }) - }).transport - } else { - provider = chain.publicClient.transport + if (bundlerUrl !== undefined) { + this.addBundler(chainId, provider, entryPointAddress!) } - const entryPointAddress = chain.entryPointAddress ?? entryPoint08Address - this.addBundler(chain.chainId, provider, entryPointAddress) } - this.isInitialized = true } bundlers: Map = new Map() @@ -59,9 +43,6 @@ export class MultichainBundlerManager implements IBundlerManager { } sendUserOperation (userOp: UserOperation): Promise { - if (!this.isInitialized) { - throw new Error('MultichainBundlerManager is not initialized') - } const provider = this.bundlers.get(userOp.chainId!)! if (!provider) { throw new Error(`No bundler found for chainId: ${userOp.chainId!}`) @@ -69,7 +50,7 @@ export class MultichainBundlerManager implements IBundlerManager { const jsonUserOp = JSON.parse(JSON.stringify(userOp, stringifyBigIntReplacer)) return provider.request({ method: 'eth_sendUserOperation', - params: [jsonUserOp, this.entryPoints.get(userOp.chainId!)] + params: [jsonUserOp, userOp.entryPointAddress] }) } } diff --git a/packages/sdk/src/sdk/builder/InternalConfig.ts b/packages/sdk/src/sdk/builder/NetworkEnvironment.ts similarity index 78% rename from packages/sdk/src/sdk/builder/InternalConfig.ts rename to packages/sdk/src/sdk/builder/NetworkEnvironment.ts index 8fd5c01..cf1fd00 100644 --- a/packages/sdk/src/sdk/builder/InternalConfig.ts +++ b/packages/sdk/src/sdk/builder/NetworkEnvironment.ts @@ -1,16 +1,13 @@ -import { MultichainContract } from '../types/index.js' -import { MultichainClient } from '../types/index.js' +import { entryPoint09Address, MultichainClient, MultichainContract } from '../types/index.js' import { EntryPointMeta, ICrossChainPaymaster } from '../../abitypes/abiTypes.js' -import { entryPoint09Address } from '../types/Constants.js' import { CrossChainConfig } from '../config/index.js' -import { getMultiChainConfig } from './GetMultiChainConfig.js' /** * Parses input configuration into configuration objects used by the builder and executor. * This includes setting up chain clients, paymasters, bundlers, and entry points across multiple chains. * @see {CrossChainConfig} for input configuration structure. */ -export class InternalConfig { +export class NetworkEnvironment { readonly chains: MultichainClient readonly paymasters: MultichainContract @@ -20,7 +17,7 @@ export class InternalConfig { readonly input: CrossChainConfig ) { this.chains = new MultichainClient() - const chains = getMultiChainConfig(this.input.chainsInfoOverride) + const chains = this.input.chainInfos chains.forEach(chain => { this.chains.addClientWithChainId(chain.publicClient, BigInt(chain.chainId)) diff --git a/packages/sdk/src/sdk/builder/index.ts b/packages/sdk/src/sdk/builder/index.ts index b479afb..934bc14 100644 --- a/packages/sdk/src/sdk/builder/index.ts +++ b/packages/sdk/src/sdk/builder/index.ts @@ -3,7 +3,7 @@ export * from './BatchBuilder.js' export * from './CrossChainBuilder.js' export * from './CrossChainExecutor.js' export * from './CrossChainVoucherCoordinator.js' -export * from './InternalConfig.js' export * from './MultichainBundlerManager.js' export * from './GetMultiChainConfig.js' +export * from './NetworkEnvironment.js' diff --git a/packages/sdk/src/sdk/config/CrossChainConfig.ts b/packages/sdk/src/sdk/config/CrossChainConfig.ts index 249b039..d30382f 100644 --- a/packages/sdk/src/sdk/config/CrossChainConfig.ts +++ b/packages/sdk/src/sdk/config/CrossChainConfig.ts @@ -3,6 +3,7 @@ import { XlpSelectionConfig } from './XlpSelectionConfig.js' import { FeeConfig } from './FeeConfig.js' import { ChainInfo } from './ChainInfo.js' import { PaymasterActions } from 'viem/account-abstraction' +import { getMultiChainConfig } from '../builder/index.js' // general config of the CrossChainSdk export type CrossChainConfig = { @@ -22,7 +23,13 @@ export type CrossChainConfig = { execTimeoutSeconds: number /** - * override default chain configuration (urls and contracts) + * per-chain info (urls, contracts) */ - chainsInfoOverride?: ChainInfo[] + chainInfos: ChainInfo[] +} + +export const defaultCrossChainConfig: CrossChainConfig = { + expireTimeSeconds: 60, + execTimeoutSeconds: 30, + chainInfos: getMultiChainConfig() } diff --git a/packages/sdk/src/sdk/types/Constants.ts b/packages/sdk/src/sdk/types/Constants.ts index 862d78d..850735d 100644 --- a/packages/sdk/src/sdk/types/Constants.ts +++ b/packages/sdk/src/sdk/types/Constants.ts @@ -4,4 +4,4 @@ export const Hex0x: Hex = '0x' export const NATIVE_ETH: Address = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' -export const entryPoint09Address: Address = '0x433709009B8330FDa32311DF1C2AFA402eD8D009' +export const entryPoint09Address: Address = '0x43370900c8de573dB349BEd8DD53b4Ebd3Cce709' diff --git a/packages/sdk/src/sdk/types/ICrossChainBuilder.ts b/packages/sdk/src/sdk/types/ICrossChainBuilder.ts index bc25152..dcd67e8 100644 --- a/packages/sdk/src/sdk/types/ICrossChainBuilder.ts +++ b/packages/sdk/src/sdk/types/ICrossChainBuilder.ts @@ -1,6 +1,7 @@ import { BatchBuilder, CrossChainExecutor } from '../builder/index.js' import { SingleChainBatch } from './SingleChainBatch.js' import { UserOperation } from './UserOperation.js' +import { IMultiChainSmartAccount } from '../account/index.js' export interface ICrossChainBuilder { @@ -8,7 +9,13 @@ export interface ICrossChainBuilder { * create a new batch, to be executed on the given chain. * @param chainId */ - createBatch (chainId: bigint): BatchBuilder; + startBatch (chainId: bigint): BatchBuilder; + + /** + * set the account to use to build and execution that coross chain operation + * must be called before build() and sign() + */ + useAccount(account: IMultiChainSmartAccount): this /** * Build an array of {@link SingleChainBatch} objects, ready to be signed @@ -24,6 +31,8 @@ export interface ICrossChainBuilder { */ buildAndSign (): Promise; + getAccount(): IMultiChainSmartAccount + /** * initialize the SDK. validate all configuration is correct. */ diff --git a/packages/sdk/src/sdk/types/ICrossChainSdk.ts b/packages/sdk/src/sdk/types/ICrossChainSdk.ts index 85b0e0d..f83b9bc 100644 --- a/packages/sdk/src/sdk/types/ICrossChainSdk.ts +++ b/packages/sdk/src/sdk/types/ICrossChainSdk.ts @@ -1,4 +1,7 @@ import { ICrossChainBuilder } from './ICrossChainBuilder.js' +import { AddressPerChain } from './MultichainContract.js' +import { MultichainToken } from './MultichainToken.js' +import { NetworkEnvironment } from '../builder/index.js' /** * CrossChainSdk is the main entry point for building cross-chain actions. @@ -10,5 +13,12 @@ export interface ICrossChainSdk { * create a builder for a cross-chain operation */ createBuilder (): ICrossChainBuilder + + /** + * create a MultichainToken with the given deployment addresses + */ + createToken (name: string, deployments: AddressPerChain): MultichainToken + + getNetworkEnv(): NetworkEnvironment } diff --git a/packages/sdk/src/sdk/types/MultichainClient.ts b/packages/sdk/src/sdk/types/MultichainClient.ts index ec487bf..1f8f45d 100644 --- a/packages/sdk/src/sdk/types/MultichainClient.ts +++ b/packages/sdk/src/sdk/types/MultichainClient.ts @@ -33,7 +33,7 @@ export class MultichainClient { this.clients.set(chainId, client); } - on (chainId: bigint): PublicClient { + clientOn (chainId: bigint): PublicClient { if (!this.clients.has(chainId)) { throw new Error(`No client found for chainId: ${chainId}. Supported chains: ${Array.from(this.clients.keys()).join(', ')}`); } @@ -48,7 +48,7 @@ export class MultichainClient { args: any[], value?: bigint }): Promise { - const client = this.on(chainId); + const client = this.clientOn(chainId); const data = encodeFunctionData({ abi, functionName, args }); const valueHex = value ? toHex(value) : undefined const ret = await client.request({ method: 'eth_call', params: [{ to, data, value: valueHex }, 'latest'] }) as Hex diff --git a/packages/sdk/src/sdk/types/MultichainContract.ts b/packages/sdk/src/sdk/types/MultichainContract.ts index 7928b4f..d746b56 100644 --- a/packages/sdk/src/sdk/types/MultichainContract.ts +++ b/packages/sdk/src/sdk/types/MultichainContract.ts @@ -67,7 +67,7 @@ export class MultichainContract implements IMultiChainEntity { } async call (chainId: bigint, functionName: string, args: any[], callOptions: any = {}): Promise { - const client = this.client.on(chainId) + const client = this.client.clientOn(chainId) const data = this.encodeFunctionData(functionName, args) const ret = await client.call({ to: this.addressOn(chainId), diff --git a/packages/sdk/src/sdk/types/MultichainToken.ts b/packages/sdk/src/sdk/types/MultichainToken.ts index 45c6f4c..5eaa207 100644 --- a/packages/sdk/src/sdk/types/MultichainToken.ts +++ b/packages/sdk/src/sdk/types/MultichainToken.ts @@ -11,7 +11,7 @@ export type TotalBalanceOfResult = { //wrapper for multichain ERC20 token operations. export class MultichainToken extends MultichainContract { - constructor (client: MultichainClient, deployments: AddressPerChain) { + constructor (name: string, client: MultichainClient, deployments: AddressPerChain) { super(client, erc20Abi, deployments) } diff --git a/packages/sdk/src/sdk/types/SdkVoucherRequest.ts b/packages/sdk/src/sdk/types/SdkVoucherRequest.ts index 78bbb6c..1d0cc10 100644 --- a/packages/sdk/src/sdk/types/SdkVoucherRequest.ts +++ b/packages/sdk/src/sdk/types/SdkVoucherRequest.ts @@ -10,4 +10,7 @@ export interface SdkVoucherRequest { target?: Address sourceChainId?: bigint destinationChainId: bigint + + // internal ID used during batch building. must be unique. + ref: string } diff --git a/packages/sdk/src/utils/getSolventXlps.ts b/packages/sdk/src/utils/getSolventXlps.ts index d390606..91f31bb 100644 --- a/packages/sdk/src/utils/getSolventXlps.ts +++ b/packages/sdk/src/utils/getSolventXlps.ts @@ -61,7 +61,7 @@ export async function getSolventXlps ( length: number = 1000 ): Promise { const paymasterAddress: Address = paymasters.addressOn(chainId) - const client: PublicClient = paymasters.client.on(chainId) + const client: PublicClient = paymasters.client.clientOn(chainId) const assets: Asset[] = mcAssets.map((asset: MultiChainAsset) => ({ erc20Token: toAddress(chainId, asset.token), amount: amountOrMinAmount(asset) diff --git a/yarn.lock b/yarn.lock index ab6e555..9a50703 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1254,6 +1254,11 @@ p-map@^7.0.2: resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.4.tgz#b81814255f542e252d5729dca4d66e5ec14935b8" integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ== +permissionless@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/permissionless/-/permissionless-0.3.2.tgz#334398236c7b2747dddbf78509471af03f8fba51" + integrity sha512-/YlcuybrW9ph+IQ0/vwRWZooEElXnsEgvXLJ/+HA1iU0kwIeTcxx4xlA6TiVZ/zTTsovS3+cSgVa8n7Sz/oTXg== + pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -1422,9 +1427,9 @@ viem@^2.39.0: ws "8.18.3" viem@^2.39.2: - version "2.39.2" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.39.2.tgz#c8f747bf22ae2ca46011a8e8ae22f26085ae3fba" - integrity sha512-EJPt+T0AkMxKvBRPFHYMLMuvcHiIhoYItkioHRGCkkm6LBSwlK6l9DNzoKA9S09LP003BiMeYddVjVso+lg2Og== + version "2.39.3" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.39.3.tgz#45884cc27d3faced1fc1d293fc2d9018b8b0789f" + integrity sha512-s11rPQRvUEdc5qHK3xT4fIk4qvgPAaLwaTFq+EbFlcJJD+Xn3R4mc9H6B6fquEiHl/mdsdbG/uKCnYpoNtHNHw== dependencies: "@noble/curves" "1.9.1" "@noble/hashes" "1.8.0"