Skip to content

Commit

Permalink
feat: import external account (#3)
Browse files Browse the repository at this point in the history
* wip: import account

* feat: wagmi

* chore: tweaks

* chore: changeset
  • Loading branch information
jxom authored Dec 3, 2024
1 parent 917817e commit f501aa4
Show file tree
Hide file tree
Showing 14 changed files with 737 additions and 105 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-dingos-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"porto": patch
---

Added `experimental_prepareImportAccount` & `experimental_importAccount` JSON-RPC methods to import external accounts (EOAs).
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Experimental Next-gen Account for Ethereum.
- [`experimental_createAccount`](#experimental_createaccount)
- [`experimental_disconnect`](#experimental_disconnect)
- [`experimental_grantSession`](#experimental_grantsession)
- [`experimental_prepareImportAccount`](#experimental_prepareImportAccount)
- [`experimental_importAccount`](#experimental_importAccount)
- [`experimental_sessions`](#experimental_sessions)
- [Available ERC-5792 Capabilities](#available-erc-5792-capabilities)
- [`atomicBatch`](#atomicbatch)
Expand Down Expand Up @@ -275,6 +277,81 @@ Grants a session on the account.
}
```

### `experimental_prepareImportAccount`

Returns a set of hex payloads to sign over to import an external account, and prepares values needed to fill context for the `experimental_importAccount` JSON-RPC method.

#### Parameters

```ts
{
method: 'experimental_prepareImportAccount',
params: [{
// Address of the account to import.
address?: `0x${string}`,

// ERC-5792 capabilities to define extended behavior.
capabilities: {
// Whether to grant a session with an optional expiry.
// Defaults to user-configured expiry on the account.
grantSession?: boolean | { expiry?: number },
}
}]
}
```

#### Returns

```ts
{
// Filled context for the `experimental_importAccount` JSON-RPC method.
context: unknown

// Hex payloads to sign over.
signPayloads: `0x${string}`[]
}
```

### `experimental_importAccount`

Imports an account.

#### Parameters

```ts
{
method: 'experimental_importAccount',
params: [{
// Context from the `experimental_prepareImportAccount` JSON-RPC method.
context: unknown,

// Signatures over the payloads returned by `experimental_prepareImportAccount`.
signatures: `0x${string}`[]
}]
}
```

#### Returns

```ts
{
// The address of the account.
address: `0x${string}`,

// ERC-5792 capabilities to define extended behavior.
capabilities: {
// The sessions granted to the account.
sessions: {
// The expiry of the session.
expiry: number,

// The ID of the session.
id: `0x${string}`,
}[],
}
}
```

### `experimental_sessions`

Lists the active sessions on the account.
Expand Down Expand Up @@ -404,6 +481,7 @@ Import via named export or `A` namespace (better autocomplete DX and does not im
- `createAccount`
- `disconnect`
- `grantSession`
- `importAccount`
- `sessions`

```ts
Expand All @@ -419,6 +497,7 @@ Import via named export or `W` namespace (better autocomplete DX and does not im
- `useCreateAccount`
- `useDisconnect`
- `useGrantSession`
- `useImportAccount`
- `useSessions`

```ts
Expand Down
84 changes: 82 additions & 2 deletions examples/wagmi/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { W } from 'porto/wagmi'
import { formatEther, parseEther } from 'viem'
import { type Hex, formatEther, parseEther } from 'viem'
import {
type BaseError,
useAccount,
Expand All @@ -9,6 +9,11 @@ import {
import { useCallsStatus, useSendCalls } from 'wagmi/experimental'

import { useState } from 'react'
import {
generatePrivateKey,
privateKeyToAccount,
privateKeyToAddress,
} from 'viem/accounts'
import { ExperimentERC20 } from './contracts'

export function App() {
Expand All @@ -23,7 +28,10 @@ export function App() {
<Mint />
</>
) : (
<Connect />
<>
<Connect />
<ImportAccount />
</>
)}
</>
)
Expand Down Expand Up @@ -101,6 +109,78 @@ function Connect() {
)
}

function ImportAccount() {
const [accountData, setAccountData] = useState<{
address: string
privateKey: string
} | null>(null)
const [grantSession, setGrantSession] = useState<boolean>(true)
const [privateKey, setPrivateKey] = useState<string>('')

const connectors = useConnectors()
const importAccount = W.useImportAccount()

return (
<div>
<h2>Import Account</h2>
<p>
<button
onClick={() => {
const privateKey = generatePrivateKey()
setPrivateKey(privateKey)
setAccountData({
privateKey,
address: privateKeyToAddress(privateKey),
})
}}
type="button"
>
Create Account
</button>
{accountData && <pre>{JSON.stringify(accountData, null, 2)}</pre>}
</p>
<div>
<input
type="text"
value={privateKey}
onChange={(e) => setPrivateKey(e.target.value)}
placeholder="Private Key"
style={{ width: '300px' }}
/>
</div>
<div>
<label>
<input
type="checkbox"
checked={grantSession}
onChange={() => setGrantSession((x) => !x)}
/>
Grant Session
</label>
</div>
{connectors
.filter((x) => x.id === 'xyz.ithaca.porto')
?.map((connector) => (
<button
key={connector.uid}
onClick={() =>
importAccount.mutate({
account: privateKeyToAccount(privateKey as Hex),
connector,
grantSession,
})
}
type="button"
>
Import Account
</button>
))}
<div>{importAccount.status}</div>
<div>{importAccount.error?.message}</div>
</div>
)
}

function Balance() {
const { address } = useAccount()
const { data: balance } = useReadContract({
Expand Down
69 changes: 68 additions & 1 deletion playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AbiFunction, Hex, Json, PublicKey, TypedData, Value } from 'ox'
import { Porto } from 'porto'
import { useEffect, useState, useSyncExternalStore } from 'react'
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react'
import { createClient, custom } from 'viem'
import { verifyMessage, verifyTypedData } from 'viem/actions'

import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { ExperimentERC20 } from './contracts'

export const porto = Porto.create()
Expand All @@ -19,6 +20,7 @@ export function App() {
<Events />
<Connect />
<Register />
<ImportAccount />
<Login />
<Disconnect />
<Accounts />
Expand Down Expand Up @@ -310,6 +312,71 @@ function GetSessions() {
)
}

function ImportAccount() {
const account = useMemo(() => privateKeyToAccount(generatePrivateKey()), [])

const [grantSession, setGrantSession] = useState<boolean>(true)
const [prepareResult, setPrepareResult] = useState<{
context: any
signPayloads: readonly Hex.Hex[]
} | null>(null)
const [result, setResult] = useState<unknown | null>(null)

return (
<div>
<h3>experimental_importAccount</h3>
<label>
<input
type="checkbox"
checked={grantSession}
onChange={() => setGrantSession((x) => !x)}
/>
Grant Session
</label>
<div>
<button
onClick={() =>
porto.provider
.request({
method: 'experimental_prepareImportAccount',
params: [
{ address: account.address, capabilities: { grantSession } },
],
})
.then(setPrepareResult)
}
type="button"
>
Prepare
</button>
<button
disabled={!prepareResult}
onClick={async () => {
const signatures = await Promise.all(
prepareResult!.signPayloads.map((hash: Hex.Hex) =>
account.sign({ hash }),
),
)
const address = await porto.provider.request({
method: 'experimental_importAccount',
params: [{ context: prepareResult!.context, signatures }],
})
setResult(address)
}}
type="button"
>
Sign & Import
</button>
</div>
{result ? (
<p>
Imported account. <pre>{JSON.stringify(result, null, 2)}</pre>
</p>
) : null}
</div>
)
}

function SendCalls() {
const [hash, setHash] = useState<string | null>(null)
return (
Expand Down
Loading

0 comments on commit f501aa4

Please sign in to comment.