Skip to content

Commit

Permalink
feat: zksync smart account (#2598)
Browse files Browse the repository at this point in the history
* feat: zksync `toSmartAccount`

* chore: format

* chore: tweaks

* chore: tweak

* chore: format

* chore: tweaks

* chore: tweaks

* chore: tweaks

* chore: format
  • Loading branch information
jxom authored Aug 21, 2024
1 parent 5f60093 commit 627274b
Show file tree
Hide file tree
Showing 16 changed files with 582 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/ninety-taxis-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added ZKsync `toSmartAccount`.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
description: Creates a Smart Account with a provided Account Implementation.
---

# Custom
# toSmartAccount

The `toSmartAccount` function allows you to create a Smart Account with a custom Account Implementation.

Expand Down
46 changes: 46 additions & 0 deletions site/pages/zksync/accounts/toMultisigSmartAccount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
description: Creates a multi-signature ZKsync Smart Account
---

# toMultisigSmartAccount (ZKsync)

Creates a multi-signature [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) from a Contract Address and the Private Key of the owner.

## Usage

```ts twoslash
import { toMultisigSmartAccount } from 'viem/zksync'

const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad8F6F4ce6aB8827279cffFb92266',
privateKeys: ['0x...', '0x...']
})
```

## Parameters

### address

- **Type:** `Hex`

Address of the deployed Account's Contract implementation.

```ts
const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus]
privateKeys: ['0x...', '0x...']
})
```

### privateKeys

- **Type:** `Hex[]`

Private Keys of the owners.

```ts
const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKeys: ['0x...', '0x...'] // [!code focus]
})
```
46 changes: 46 additions & 0 deletions site/pages/zksync/accounts/toSinglesigSmartAccount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
description: Creates a single-signature ZKsync Smart Account
---

# toSinglesigSmartAccount (ZKsync)

Creates a single-signature [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) from a Contract Address and the Private Key of the owner.

## Usage

```ts twoslash
import { toSinglesigSmartAccount } from 'viem/zksync'

const account = toSinglesigSmartAccount({
address: '0xf39Fd6e51aad8F6F4ce6aB8827279cffFb92266',
privateKey: '0x...'
})
```

## Parameters

### address

- **Type:** `Hex`

Address of the deployed Account's Contract implementation.

```ts
const account = toSinglesigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus]
privateKey: '0x...'
})
```

### privateKey

- **Type:** `Hex`

Private Key of the owner.

```ts
const account = toSinglesigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKey: '0x...' // [!code focus]
})
```
53 changes: 53 additions & 0 deletions site/pages/zksync/accounts/toSmartAccount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
description: Creates a ZKsync Smart Account
---

# toSmartAccount (ZKsync)

Creates a [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) from a Contract Address and a custom sign function.

## Usage

```ts twoslash
import { toSmartAccount } from 'viem/zksync'

const account = toSmartAccount({
address: '0xf39Fd6e51aad8F6F4ce6aB8827279cffFb92266',
async sign({ hash }) {
// ... signing logic
return '0x...'
}
})
```

## Parameters

### address

- **Type:** `Hex`

Address of the deployed Account's Contract implementation.

```ts
const account = toSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus]
async sign({ hash }) {
// ...
}
})
```

### sign

- **Type:** `({ hash: Hex }) => Hex`

Custom sign function for the Smart Account.

```ts
const account = toSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
async sign({ hash }) { // [!code focus]
// ... // [!code focus]
} // [!code focus]
})
```
17 changes: 17 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,23 @@ export const sidebar = {
{ text: 'Chains', link: '/zksync/chains' },
],
},
{
text: 'Smart Accounts',
items: [
{
text: 'Singlesig',
link: '/zksync/accounts/toSinglesigSmartAccount',
},
{
text: 'Multisig',
link: '/zksync/accounts/toMultisigSmartAccount',
},
{
text: 'Custom',
link: '/zksync/accounts/toSmartAccount',
},
],
},
{
text: 'EIP-712 Actions',
items: [
Expand Down
4 changes: 3 additions & 1 deletion site/vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export default defineConfig({
rootDir: '.',
search: {
boostDocument(documentId) {
if (documentId.startsWith('pages/docs')) return 2
if (documentId.startsWith('pages/docs')) return 3
if (documentId.startsWith('pages/account-abstraction')) return 2
if (documentId.startsWith('pages/experimental')) return 2
return 1
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/accounts/privateKeyToAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function privateKeyToAccount(
return signTransaction({ privateKey, transaction, serializer })
},
async signTypedData(typedData) {
return signTypedData({ ...typedData, privateKey })
return signTypedData({ ...typedData, privateKey } as any)
},
})

Expand Down
83 changes: 83 additions & 0 deletions src/zksync/accounts/toMultisigSmartAccount.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { expect, test } from 'vitest'

import { accounts, typedData } from '~test/src/constants.js'

import { parseEther, parseGwei } from '../../utils/index.js'
import { toMultisigSmartAccount } from './toMultisigSmartAccount.js'

test('default', () => {
expect(
toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKeys: [accounts[0].privateKey, accounts[1].privateKey],
}),
).toMatchInlineSnapshot(`
{
"address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"experimental_signAuthorization": undefined,
"nonceManager": undefined,
"sign": [Function],
"signMessage": [Function],
"signTransaction": [Function],
"signTypedData": [Function],
"source": "smartAccountZksync",
"type": "local",
}
`)
})

test('sign', async () => {
const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKeys: [accounts[0].privateKey, accounts[1].privateKey],
})
expect(
await account.sign({
hash: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68',
}),
).toMatchInlineSnapshot(
`"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b02de21de49d98cf93ef790a262702dcc711b3f2ce0a971e3b50caea43cfc07cb34eabfd4d39eff886015fb2c42ec4265aeaf5ee34a78c75309fdc1165cb659521c"`,
)
})

test('sign message', async () => {
const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKeys: [accounts[0].privateKey, accounts[1].privateKey],
})
expect(
await account.signMessage({ message: 'hello world' }),
).toMatchInlineSnapshot(
`"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b02de21de49d98cf93ef790a262702dcc711b3f2ce0a971e3b50caea43cfc07cb34eabfd4d39eff886015fb2c42ec4265aeaf5ee34a78c75309fdc1165cb659521c"`,
)
})

test('sign transaction', async () => {
const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKeys: [accounts[0].privateKey, accounts[1].privateKey],
})
expect(
await account.signTransaction({
chainId: 1,
maxFeePerGas: parseGwei('20'),
gas: 21000n,
to: accounts[1].address,
value: parseEther('1'),
}),
).toMatchInlineSnapshot(
`"0x71f8cc80808504a817c8008252089470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000000180800194f39fd6e51aad88f6f4ce6ab8827279cfffb9226682c350c0b882f40a2d2ae9638056cafbe9083c7125edc8555e0e715db0984dd859a5c6dfac5720f36fd0b32bef4d6d75c62f220e59c5fb60c244ca3b361e750985ee5c3a09311c4e5f2cafb92b15ff828d4f8fb34cd3058355428586539495fd0075919cb3cd0d0d33b1e617948f2938f0be2895e4eb4a58a4bcd1a57874ed2c2d235424cf03271bc0"`,
)
})

test('sign typed data', async () => {
const account = toMultisigSmartAccount({
address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
privateKeys: [accounts[0].privateKey, accounts[1].privateKey],
})
expect(
await account.signTypedData({ ...typedData.basic, primaryType: 'Mail' }),
).toMatchInlineSnapshot(
`"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b7f65cd2428f35d93b843da90d299ccabe834c0068b0d4035112ad1345abf962b579854cad5911d725fc1000e6e4ebfb4c41e233f66153857b5c017cb3115879a1c"`,
)
})
37 changes: 37 additions & 0 deletions src/zksync/accounts/toMultisigSmartAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Address } from 'abitype'

import { sign } from '../../accounts/utils/sign.js'
import type { Hex } from '../../types/misc.js'
import { concatHex } from '../../utils/index.js'
import type { ZksyncSmartAccount } from '../types/account.js'
import { toSmartAccount } from './toSmartAccount.js'

export type ToMultisigSmartAccountParameters = {
/** Address of the deployed Account's Contract implementation. */
address: Address
/** Array of Private Keys belonging to the owners. */
privateKeys: readonly Hex[]
}

/**
* Creates a [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts)
* from a Contract Address and an array of Private Keys belonging to the owners.
*/
export function toMultisigSmartAccount(
parameters: ToMultisigSmartAccountParameters,
): ZksyncSmartAccount {
const { address, privateKeys } = parameters

return toSmartAccount({
address,
async sign({ hash }) {
return concatHex(
await Promise.all(
privateKeys.map((privateKey) =>
sign({ hash, privateKey, to: 'hex' }),
),
),
)
},
})
}
Loading

0 comments on commit 627274b

Please sign in to comment.