Skip to content

Commit 312aa8e

Browse files
authored
Simpleaccount (#2)
* added simpleaccount * updated deployment * rename networkenv
1 parent c0adf6c commit 312aa8e

23 files changed

+268
-103
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ node_modules
33
/.idea/
44
/packages/sdk/dist/
55
/packages/accounts/dist/
6+
/yarn-error.log

packages/accounts/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"clean": "rm -rf dist"
77
},
88
"version": "0.1.0",
9-
"main": "index.js",
10-
"license": "MIT"
9+
"main": "./dist/index.js",
10+
"license": "MIT",
11+
"dependencies": {
12+
"permissionless": "^0.3.2"
13+
}
1114
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { SmartAccount } from 'viem/account-abstraction'
2+
import { Call, Hex, LocalAccount, } from 'viem'
3+
import {
4+
asCall,
5+
BaseMultichainSmartAccount,
6+
FunctionCall,
7+
ICrossChainSdk,
8+
MultichainBundlerManager,
9+
UserOperation
10+
} from '@eil-protocol/sdk'
11+
import { toSimpleSmartAccount } from 'permissionless/accounts'
12+
13+
/**
14+
* a simple MultichainAccount. using SimpleSmartAccount on each chain.
15+
* Note that the signature method called for each SimpleSmartAccount instead separately.
16+
* Also, its encoding can't support dynamic calls.
17+
*/
18+
export class MultiChainSmartAccount extends BaseMultichainSmartAccount {
19+
20+
accounts: Map<bigint, SmartAccount> = new Map()
21+
22+
/**
23+
* Creates a new MultiChainSmartAccount instance
24+
* @param owner - The account owner used for multichain signing. Must belong to the same owner across all chains.
25+
* @param sdk - the sdk configuration to read supported network
26+
* @param accounts - Array of {@link SmartAccount} instances for different chains (optional).
27+
* @notice The owner must have signing capabilities for all chains.
28+
* @dev Note that SimpleAccount API doesn't expose the owner directly.
29+
*/
30+
protected constructor (
31+
readonly owner: LocalAccount,
32+
sdk: ICrossChainSdk,
33+
accounts: SmartAccount[]
34+
) {
35+
36+
const bundlerManager = new MultichainBundlerManager(sdk.getNetworkEnv().input.chainInfos)
37+
super(bundlerManager)
38+
this.addAccounts(accounts)
39+
}
40+
41+
static async create (
42+
owner: LocalAccount,
43+
sdk: ICrossChainSdk,
44+
accounts?: SmartAccount[]
45+
): Promise<MultiChainSmartAccount> {
46+
const networkEnv = sdk.getNetworkEnv()
47+
if (accounts == null) {
48+
accounts = []
49+
for (const chain of networkEnv.input.chainInfos) {
50+
let client = networkEnv.chains.clientOn(chain.chainId)
51+
const entryPointAddress = networkEnv.entrypoints.addressOn(chain.chainId)
52+
53+
const factoryAddress = '0x2862B77afcF4405e766328E697E0236b9974b8fa' //ep9 simpleAccount factory
54+
const account: SmartAccount = await toSimpleSmartAccount({
55+
owner,
56+
client,
57+
entryPoint: {
58+
address: entryPointAddress,
59+
version: '0.8'
60+
},
61+
factoryAddress
62+
})
63+
accounts.push(account)
64+
}
65+
}
66+
return new MultiChainSmartAccount(
67+
owner,
68+
sdk,
69+
accounts
70+
)
71+
}
72+
73+
//add chain-specific instances of the account.
74+
//TODO: currently needs viem "SmartAccount" for each chain. better unify it
75+
// (some methods are chain-specific, like "getNonce". others (like encode) are not.
76+
protected addAccounts (accounts: SmartAccount[]) {
77+
for (const account of accounts) {
78+
const chainId = BigInt(account.client.chain?.id!)
79+
if (this.accounts.has(chainId)) {
80+
throw new Error(`Account object already exists for chainId: ${chainId}`)
81+
}
82+
this.accounts.set(chainId, account)
83+
}
84+
}
85+
86+
hasAddress (chainId: bigint): boolean {
87+
return this.accounts.has(chainId)
88+
}
89+
90+
contractOn (chainId: bigint): SmartAccount {
91+
if (!this.accounts.has(chainId)) {
92+
throw new Error(`No account found for chainId: ${chainId}`)
93+
}
94+
return this.accounts.get(chainId)!
95+
}
96+
97+
async signUserOps (userOps: UserOperation[]): Promise<UserOperation[]> {
98+
// naive implementation: sign using each per-chain account.
99+
const result: UserOperation[] = []
100+
for (const userOp of userOps) {
101+
const chainId = userOp.chainId
102+
if (chainId == null) {
103+
throw new Error(`signUserOps: can only sign userOps with chain`)
104+
}
105+
const account = this.contractOn(chainId)
106+
const signature = await account.signUserOperation(userOp as any)
107+
result.push({ ...userOp, signature })
108+
}
109+
return result
110+
}
111+
112+
async encodeCalls (chainId: bigint, calls: Array<Call | FunctionCall>): Promise<Hex> {
113+
const plainCalls: Call[] = calls.map(call => asCall(chainId, call))
114+
return this.contractOn(chainId).encodeCalls(plainCalls)
115+
}
116+
}
117+

packages/accounts/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
console.log('accounts')
1+
export * from './ambire/AmbireMultiChainSmartAccount.js'
2+
export * from './MultiChainSmartAccount.js'
Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
1-
import { CrossChainConfig } from './config/index.js'
2-
import { ICrossChainBuilder, ICrossChainSdk, IJsonRpcProvider } from './types/index.js'
3-
import { CrossChainBuilder, InternalConfig } from './builder/index.js'
4-
import { IMultiChainSmartAccount } from './account/index.js'
1+
import { CrossChainConfig, defaultCrossChainConfig } from './config/index.js'
2+
import { AddressPerChain, ICrossChainBuilder, ICrossChainSdk, MultichainToken } from './types/index.js'
3+
import { CrossChainBuilder, NetworkEnvironment } from './builder/index.js'
54

65
/**
76
* This class is the main component for building cross-chain actions.
87
* It holds the configuration and is used to create the {@link BatchBuilder}.
98
*/
109
export class CrossChainSdk implements ICrossChainSdk {
1110

12-
config: InternalConfig
11+
networkEnv: NetworkEnvironment
1312

1413
constructor (
15-
readonly account: IMultiChainSmartAccount,
16-
config: CrossChainConfig,
17-
readonly walletProvider: IJsonRpcProvider | undefined = undefined
14+
config: CrossChainConfig = defaultCrossChainConfig
1815
) {
19-
this.config = new InternalConfig(config)
16+
this.networkEnv = new NetworkEnvironment(config)
2017
}
2118

2219
/**
2320
* create a builder for a cross-chain operation
2421
*/
2522
createBuilder (): ICrossChainBuilder {
26-
return new CrossChainBuilder(this.config, this.account)
23+
return new CrossChainBuilder(this.networkEnv)
24+
}
25+
26+
/**
27+
* create a MultichainToken with the given deployment addresses
28+
*/
29+
createToken (name: string, deployments: AddressPerChain): MultichainToken {
30+
return new MultichainToken(name, this.networkEnv.chains, deployments)
31+
}
32+
33+
getNetworkEnv (): NetworkEnvironment {
34+
return this.networkEnv
2735
}
2836
}
2937

packages/sdk/src/sdk/actions/VoucherRequestAction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Address, Call } from 'viem'
22

33
import { BaseAction, BatchBuilder, FunctionCall, SdkVoucherRequest, toAddress } from '../index.js'
4-
import { NATIVE_ETH } from '../types/Constants.js'
4+
import { NATIVE_ETH } from '../types/index.js'
55

66
/**
77
* The internal class defining an action to lock the user deposit for the specified {@link SdkVoucherRequest}.

packages/sdk/src/sdk/builder/BatchBuilder.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Address, Hex, PrivateKeyAccount, publicActions } from 'viem'
1+
import { Address, Hex, hexToBigInt, PrivateKeyAccount, publicActions } from 'viem'
22
import { BaseAction, FunctionCallAction, prepareCallWithRuntimeVars, VoucherRequestAction } from '../actions/index.js'
33
import { CrossChainBuilder } from './CrossChainBuilder.js'
44
import { CrossChainVoucherCoordinator } from './CrossChainVoucherCoordinator.js'
5-
import { InternalConfig } from './InternalConfig.js'
5+
import { NetworkEnvironment } from './NetworkEnvironment.js'
66
import {
7+
ICrossChainBuilder,
78
InternalVoucherInfo,
89
isCall,
910
isValidAddress,
@@ -15,9 +16,8 @@ import {
1516
} from '../types/index.js'
1617
import { appendPaymasterSignature, getUserOpHash } from '../index.js'
1718
import { assert } from '../sdkUtils/SdkUtils.js'
18-
import { IMultiChainSmartAccount } from '../account/index.js'
1919
import { Asset } from '../../contractTypes/Asset.js'
20-
import { abiEncodePaymasterData } from '../../utils/index.js'
20+
import { abiEncodePaymasterData, nowSeconds } from '../../utils/index.js'
2121

2222
/**
2323
* Return the minimum amount for an asset.
@@ -56,14 +56,18 @@ export class BatchBuilder {
5656
private _vars: Set<string> = new Set()
5757

5858
constructor (
59+
private readonly parentBuilder: ICrossChainBuilder,
5960
private readonly ephemeralSigner: PrivateKeyAccount,
6061
private readonly coordinator: CrossChainVoucherCoordinator,
61-
readonly config: InternalConfig,
62-
private readonly smartAccount: IMultiChainSmartAccount,
62+
readonly config: NetworkEnvironment,
6363
readonly paymaster: `0x${string}`,
6464
readonly chainId: bigint
6565
) {}
6666

67+
endBatch (): ICrossChainBuilder {
68+
return this.parentBuilder
69+
}
70+
6771
/**
6872
* Add a new dynamic runtime variable to the batch.
6973
* Variables can be used to store values that are not known at the time of batch creation.
@@ -127,7 +131,7 @@ export class BatchBuilder {
127131
} else {
128132
assert(req.sourceChainId == this.chainId, `Voucher request sourceChainId ${req.sourceChainId} does not match batch chainId ${this.chainId}`)
129133
}
130-
assert(!this.coordinator.has(req), `Voucher request ${req} already exists in this BatchBuilder`)
134+
assert(!this.coordinator.has(req), `Voucher request ${req.ref} already exists in this BatchBuilder`)
131135

132136
req.tokens.forEach(token => {
133137
if (!isValidAddress(req.sourceChainId!, token.token)) {
@@ -138,7 +142,7 @@ export class BatchBuilder {
138142
}
139143
})
140144

141-
this.coordinator.set(req, {
145+
this.coordinator.set({
142146
voucher: req,
143147
sourceBatch: this,
144148
})
@@ -155,13 +159,14 @@ export class BatchBuilder {
155159
/**
156160
* Use the {@link SdkVoucherRequest} created in an earlier batch to move tokens to this chain.
157161
*/
158-
useVoucher (voucher: SdkVoucherRequest): this {
162+
useVoucher (refId: string): this {
159163
this.assertNotBuilt()
160-
const internalVoucherInfo = this.coordinator.getVoucherInternalInfo(voucher)
161-
assert(internalVoucherInfo != null, `Voucher request ${voucher} not found in action builder`)
164+
const internalVoucherInfo = this.coordinator.getVoucherInternalInfo(refId)
165+
const voucher = internalVoucherInfo?.voucher!
166+
assert(internalVoucherInfo != null, `Voucher request ${refId} not found in action builder`)
162167
assert(this.userOpOverrides?.paymaster == null && this.userOpOverrides?.paymasterData == null,
163168
`Cannot override paymaster or paymasterData in a batch that uses vouchers.`)
164-
assert(internalVoucherInfo.destBatch == undefined, `Voucher request ${voucher} already used`)
169+
assert(internalVoucherInfo.destBatch == undefined, `Voucher request ${refId} already used`)
165170
assert(voucher.destinationChainId == this.chainId,
166171
`Voucher request is for chain ${voucher.destinationChainId}, but batch is for chain ${this.chainId}`)
167172
internalVoucherInfo.destBatch = this
@@ -179,7 +184,7 @@ export class BatchBuilder {
179184
for (const v of allVoucherRequests) {
180185
if (v.destinationChainId === this.chainId) {
181186
added = true
182-
this.useVoucher(v)
187+
this.useVoucher(v.ref)
183188
}
184189
}
185190
assert(added, `No voucher requests found for chain ${this.chainId}`)
@@ -194,7 +199,8 @@ export class BatchBuilder {
194199
async createUserOp (): Promise<UserOperation> {
195200
const chainId = this.chainId
196201

197-
const smartAccount = this.smartAccount.contractOn(chainId)
202+
const mcAccount = this.parentBuilder.getAccount()
203+
const smartAccount = mcAccount.contractOn(chainId)
198204
const allCalls = await Promise.all(this.actions.map((action) => {
199205
return action.encodeCall(this)
200206
}))
@@ -209,14 +215,15 @@ export class BatchBuilder {
209215
smartAccount.getAddress(),
210216
smartAccount.getNonce(),
211217
smartAccount.getFactoryArgs(),
212-
calls.length == 0 ? '0x' : this.smartAccount.encodeCalls(chainId, calls)
218+
calls.length == 0 ? '0x' : mcAccount.encodeCalls(chainId, calls)
213219
])
214220

221+
const nonce1 = BigInt(nowSeconds()) << 64n
215222
const { maxFeePerGas, maxPriorityFeePerGas } = await smartAccount.client.extend(publicActions).estimateFeesPerGas()
216223
let userOp = {
217224
chainId,
218225
sender,
219-
nonce,
226+
nonce: nonce1,
220227
factory,
221228
factoryData,
222229
callData,
@@ -312,7 +319,7 @@ export class BatchBuilder {
312319
}
313320

314321
getVoucherInternalInfo (voucher: SdkVoucherRequest): InternalVoucherInfo | undefined {
315-
return this.coordinator.getVoucherInternalInfo(voucher)
322+
return this.coordinator.getVoucherInternalInfo(voucher.ref)
316323
}
317324

318325
getOutVoucherRequests (): SdkVoucherRequest[] {

0 commit comments

Comments
 (0)