Experimental Next-gen Account for Ethereum.
Warning
This repository is work-in-progress and highly experimental. It is recommended not to use it in production just yet.
- Install
- Usage
- JSON-RPC Reference
- Available ERC-5792 Capabilities
- Wagmi Reference
- FAQs
- Development
- License
pnpm i porto
The example below demonstrates usage of Porto's EIP-1193 Provider:
import { Porto } from 'porto'
const porto = Porto.create()
const { accounts } = await porto.provider.request({
method: 'wallet_connect'
})
Porto can be used in conjunction with Wagmi to provide a seamless experience for developers and end-users.
Get started with Wagmi by following the official guide.
After you have set up Wagmi, you can set up Porto by calling Porto.create()
. This will automatically
inject a Porto-configured EIP-1193 Provider into your Wagmi instance via EIP-6963: Multi Injected Provider Discovery.
import { Porto } from 'porto'
import { http, createConfig, createStorage } from 'wagmi'
import { odysseyTestnet } from 'wagmi/chains'
Porto.create()
export const wagmiConfig = createConfig({
chains: [odysseyTestnet],
storage: createStorage({ storage: localStorage }),
transports: {
[odysseyTestnet.id]: http(),
},
})
This means you can now use Wagmi-compatible Hooks like useConnect
. For more info, check out the Wagmi Reference.
import { Hooks } from 'porto/wagmi'
import { useConnectors } from 'wagmi'
function Connect() {
const connect = Hooks.useConnect()
const connectors = useConnectors()
return connectors?.map((connector) => (
<div key={connector.uid}>
<button
onClick={() =>
connect.mutate({
connector,
})
}
>
Login
</button>
<button
onClick={() =>
connect.mutate({
connector,
createAccount: true,
}
)}
>
Register
</button>
</div>
))
}
Porto implements the following standardized wallet JSON-RPC methods:
eth_accounts
eth_requestAccounts
eth_sendTransaction
eth_signTypedData_v4
personal_sign
wallet_connect
(ERC-7846: Wallet Connection API)wallet_disconnect
(ERC-7846: Wallet Connection API)wallet_getCapabilities
(ERC-5792: Wallet Call API)wallet_getCallsStatus
(ERC-5792: Wallet Call API)wallet_sendCalls
(ERC-5792: Wallet Call API)
In addition to the above, Porto implements the following experimental JSON-RPC methods:
Note
These JSON-RPC methods intend to be upstreamed as an ERC (or deprecated in favor of upcoming/existing ERCs) in the near future. They are purposefully minimalistic and intend to be iterated on.
Authorizes a key that can perform actions on behalf of the account.
If key.role
is absent, Porto will generate a new arbitrary "session" key to authorize on the account.
The following role
values are supported:
admin
:- CAN have an infinite expiry
- CAN have call scopes (
callScopes
) - CAN execute calls (e.g.
eth_sendTransaction
,wallet_sendCalls
) - CAN sign arbitrary data (e.g.
personal_sign
,eth_signTypedData_v4
)
session
:- MUST have a limited expiry
- MUST have call scopes (
callScopes
) - CAN only execute calls
- CANNOT sign arbitrary data
Minimal alternative to the draft ERC-7715 specification. We hope to upstream concepts from this method and eventually use ERC-7715 or similar.
type Request = {
method: 'experimental_authorizeKey',
params: [{
// Address of the account to authorize a key on.
address?: `0x${string}`
// Key to authorize on the account.
key?: {
// Call scopes to authorize on the key.
callScopes?: {
// Function signature or 4-byte selector.
signature?: string
// Authorized target address.
to?: `0x${string}`
}[]
// Expiry of the key.
expiry?: number
// Public key.
publicKey?: `0x${string}`,
// Role of key.
role?: 'admin' | 'session',
// Type of key.
type?: 'p256' | 'secp256k1' | 'webauthn-p256',
}
}]
}
type Response = {
callScopes?: {
signature?: string
to?: `0x${string}`
}[]
expiry: number,
publicKey: `0x${string}`,
role: 'admin' | 'session',
type: 'p256' | 'secp256k1' | 'webauthn-p256',
}
// Generate and authorize a session key with two call scopes.
const key = await porto.provider.request({
method: 'experimental_authorizeKey',
params: [{
key: {
callScopes: [
{
signature: 'mint()',
to: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
},
{
signature: 'transfer(address,uint256)',
to: '0xcafebabecafebabecafebabecafebabecafebabe'
},
]
}
}],
})
// Provide and authorize a P256 session key.
const key = await porto.provider.request({
method: 'experimental_authorizeKey',
params: [{
key: {
callScopes: [
{
signature: 'mint()',
to: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
},
],
publicKey: '0x...',
type: 'p256',
}
}],
})
Creates (and connects) a new account.
type Request = {
method: 'experimental_createAccount',
params: [{
// Chain ID to create the account on.
chainId?: Hex.Hex
// Label for the account.
// Used as the Passkey credential display name.
label?: string
}]
}
// Address of the created account.
type Response = `0x${string}`
// Creates an account and associates its WebAuthn credential with a label.
const address = await porto.provider.request({
method: 'experimental_createAccount',
params: [{ label: 'My Example Account' }],
})
Returns a set of hex payloads to sign over to upgrade an existing EOA to a Porto Account. Additionally, it will prepare values needed to fill context for the experimental_createAccount
JSON-RPC method.
type Request = {
method: 'experimental_prepareCreateAccount',
params: [{
// Address of the account to import.
address?: `0x${string}`,
// ERC-5792 capabilities to define extended behavior.
capabilities: {
// Whether to authorize a key with an optional expiry.
authorizeKey?: {
callScopes?: {
signature?: string
to?: `0x${string}`
}[]
expiry?: number
publicKey?: `0x${string}`
role?: 'admin' | 'session'
type?: 'p256' | 'secp256k1' | 'webauthn-p256'
},
}
}]
}
type Response = {
// Filled context for the `experimental_createAccount` JSON-RPC method.
context: unknown
// Hex payloads to sign over.
signPayloads: `0x${string}`[]
}
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
// Create a random EOA.
const eoa = privateKeyToAccount(generatePrivateKey())
// Extract the payloads to sign over to upgrade the EOA to a Porto Account.
const { context, signPayloads } = await porto.provider.request({
method: 'experimental_prepareCreateAccount',
params: [{ address: eoa.address }],
})
// Sign over the payloads.
const signatures = signPayloads.map((payload) => eoa.sign(payload))
// Upgrade the EOA to a Porto Account.
const { address, capabilities } = await porto.provider.request({
method: 'experimental_createAccount',
params: [{ context, signatures }],
})
Lists active keys that can perform actions on behalf of the account.
type Request = {
method: 'experimental_keys',
params: [{
// Address of the account to list keys on.
address?: `0x${string}`
}]
}
type Response = {
callScopes?: {
signature?: string
to?: `0x${string}`
}[]
expiry: number,
publicKey: `0x${string}`,
role: 'admin' | 'session',
type: 'p256' | 'secp256k1' | 'webauthn-p256'
}[]
const keys = await porto.provider.request({
method: 'experimental_keys',
})
Revokes a key.
type Request = {
method: 'experimental_revokeKey',
params: [{
// Address of the account to revoke a key on.
address?: `0x${string}`
// Public key of the key to revoke.
publicKey: `0x${string}`
}]
}
await porto.provider.request({
method: 'experimental_revokeKey',
params: [{ publicKey: '0x...' }],
})
Porto implements the following ERC-5792 capabilities to define extended behavior:
The Porto Account supports atomic batch calls. This means that multiple calls will be executed in a single transaction upon using wallet_sendCalls
.
Porto supports programmatic account creation.
Accounts may be created via the experimental_createAccount
JSON-RPC method.
Example:
{ method: 'experimental_createAccount' }
Accounts may be created upon connection with the createAccount
capability on the wallet_connect
JSON-RPC method.
Example:
{
method: 'wallet_connect',
params: [{
capabilities: {
createAccount: true
// OR
createAccount: { label: "My Example Account" }
}
}]
}
Porto supports account key management (ie. authorized keys & their scopes).
Keys may be authorized via the experimental_authorizeKey
JSON-RPC method.
If key.role
is absent, Porto will generate a new arbitrary "session" key to authorize on the account.
Example:
{
method: 'experimental_authorizeKey',
params: [{
address: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbe',
key: {
callScopes: [{
signature: 'mint()',
to: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbe',
}],
expiry: 1727078400,
}
}]
}
Keys may be authorized upon connection with the authorizeKey
capability on the wallet_connect
JSON-RPC method.
If authorizeKey.role
is absent, Porto will generate a new arbitrary "session" key to authorize on the account.
Example:
{
method: 'wallet_connect',
params: [{
capabilities: {
authorizeKey: {
callScopes: [{
signature: 'mint()',
to: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbe',
}],
expiry: 1727078400,
}
}
}]
}
If a key is authorized upon connection, the wallet_connect
JSON-RPC method will return the key on the capabilities.keys
parameter of the response.
Example:
{
accounts: [{
address: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbe',
capabilities: {
keys: [{
callScopes: [{
signature: 'mint()',
to: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbe',
}],
expiry: 1727078400,
publicKey: '0x...',
role: 'session',
type: 'p256'
}]
}
}],
}
Porto implements the following Wagmi VanillaJS Actions and React Hooks that map directly to the experimental JSON-RPC methods.
Note
Porto only supports the React version of Wagmi at the moment. If you are interested in adding support for other Wagmi Adapters, please create a Pull Request.
Import via named export or Actions
namespace (better autocomplete DX and does not impact tree shaking).
authorizeKey
connect
createAccount
disconnect
keys
revokeKey
upgradeAccount
import { Actions } from 'porto/wagmi' // Actions.connect()
import { connect } from 'porto/wagmi/Actions'
Import via named export or Hooks
namespace (better autocomplete DX and does not impact tree shaking).
useAuthorizeKey
useConnect
useCreateAccount
useDisconnect
useKeys
useRevokeKey
useUpgradeAccount
import { Hooks } from 'porto/wagmi' // Hooks.useConnect()
import { useConnect } from 'porto/wagmi/Hooks'
Any EOA can be used see experimental_prepareCreateAccount
.
Yes, see revokable
on the Account contract.
Yes, this can be done by calling experimental_authorizeKey
with an unix timestamp.
Currently full control over the account is granted, but in the future this can be more restricted (see execute
).
# Install pnpm
$ curl -fsSL https://get.pnpm.io/install.sh | sh -
$ pnpm install # Install modules
$ pnpm wagmi generate # get ABIs, etc.
$ pnpm dev # Run playground
# Install Foundry
$ foundryup
$ forge build --config-path ./contracts/foundry.toml # Build
$ forge test --config-path ./contracts/foundry.toml # Test
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in these packages by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.