RISE Logo-Light

Viem Integration

Using RISE Wallet with Viem for low-level control

This guide covers integrating RISE Wallet with Viem, a TypeScript interface for Ethereum that provides low-level access to blockchain functionality.

Overview

RISE Wallet provides first-class Viem support through custom actions and account implementations. This integration is ideal for developers who need fine-grained control over wallet operations.

Installation

npm i rise-wallet viem
pnpm add rise-wallet viem
yarn add rise-wallet viem
bun add rise-wallet viem

Basic Usage

Creating a Client

Use Rise Wallet's EIP-1193 provider with Viem's custom transport:

import { createClient, custom } from "viem";
import { RiseWallet } from "rise-wallet";

// Create Rise Wallet instance
const rw = RiseWallet.create();

// Create Viem client with Rise Wallet provider
const client = createClient({
  transport: custom(rw.provider),
});

Using Wallet Actions

RISE Wallet exports a WalletActions module with Rise Wallet-specific functionality:

import { WalletActions } from "rise-wallet/viem";

// Connect to wallet
const { accounts } = await WalletActions.connect(client);
console.log("Connected account:", accounts[0]);

// Get user assets
const assets = await WalletActions.getAssets(client);
console.log("User assets:", assets);

Standard Viem Actions

All standard Viem wallet actions work with RISE Wallet:

Send Transaction

import { parseEther } from "viem";
import * as Actions from "viem/actions";

const hash = await Actions.sendTransaction(client, {
  to: "0x...",
  value: parseEther("0.001"),
});

console.log("Transaction hash:", hash);

Send Calls (EIP-5792)

const result = await Actions.sendCalls(client, {
  account: "0x...",
  calls: [
    {
      to: "0x...",
      data: "0x...",
    },
  ],
});

Sign Message

const signature = await Actions.signMessage(client, {
  account: "0x...",
  message: "Hello RISE!",
});

RISE Wallet Actions

Grant Permissions

Create session keys with specific permissions:

import { Key } from "rise-wallet/viem";
import { keccak256, toHex, parseEther } from "viem";

const permissions = await WalletActions.grantPermissions(client, {
  key: Key.createP256(), // Generate new P256 key
  expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour
  permissions: {
    calls: [
      {
        to: "0x...", // Contract address
        signature: keccak256(toHex("transfer(address,uint256)")).slice(0, 10),
      }
    ],
    spend: [
      {
        limit: parseEther("50"),
        period: "minute",
        token: "0x...",
      }
    ],
  },
});

Get Permissions

View active permissions:

const permissions = await WalletActions.getPermissions(client);

permissions.forEach(permission => {
  console.log("Key:", permission.key);
  console.log("Expiry:", permission.expiry);
  console.log("Allowed calls:", permission.calls);
});

Revoke Permissions

Remove granted permissions:

await WalletActions.revokePermissions(client, {
  keyId: "0x...",
});

Key Management

RISE Wallet provides utilities for working with different key types:

P256 Keys

import { Key, PublicKey } from "rise-wallet/viem";

// Create new P256 key
const key = Key.createP256();

// Import existing key
const imported = Key.fromP256({
  privateKey: "0x...",
});

// Sign with key
const signature = await Key.sign({
  payload: "0x...",
  privateKey: key.privateKey,
});

WebAuthn Keys

// Create WebAuthn credential
const credential = await Key.createWebAuthnP256({
  user: {
    name: "alice@example.com",
    displayName: "Alice",
  },
});

// Use for signing
const webAuthnKey = Key.fromWebAuthnP256({
  credential,
  publicKey: credential.publicKey,
});

Advanced Patterns

Direct Provider Access

For maximum control, access the provider directly:

const provider = await rw.provider;

// Use JSON-RPC methods directly
const result = await provider.request({
  method: "wallet_prepareCalls",
  params: [{
    calls: [{
      to: "0x...",
      data: "0x...",
    }],
    chainId: "0x...",
    from: "0x...",
  }],
});

Session Key Signing

Sign transactions with session keys:

import { P256, Signature } from "ox";

// Prepare transaction
const { digest, ...request } = await provider.request({
  method: "wallet_prepareCalls",
  params: [{
    calls: [...],
    key: { publicKey, type: "p256" },
  }],
});

// Sign with session key
const signature = Signature.toHex(
  P256.sign({ payload: digest, privateKey })
);

// Send signed transaction
const result = await provider.request({
  method: "wallet_sendPreparedCalls",
  params: [{ ...request, signature }],
});

Custom Actions

Create your own actions by extending Viem:

import { type Client } from "viem";

export async function myCustomAction(client: Client, params: any) {
  return client.request({
    method: "my_custom_method",
    params,
  });
}

// Use with client.extend()
const extendedClient = client.extend(() => ({
  myCustomAction,
}));

await extendedClient.myCustomAction({ ... });

WebSocket Support

For realtime updates, use WebSocket transport:

import { createPublicClient, webSocket } from "viem";
import { riseTestnet } from "viem/chains";

const wsClient = createPublicClient({
  chain: riseTestnet,
  transport: webSocket("wss://testnet.riselabs.xyz/ws"),
});

// Watch for events in realtime
wsClient.watchContractEvent({
  address: "0x...",
  abi: [...],
  eventName: "Transfer",
  onLogs: (logs) => {
    console.log("Transfer events:", logs);
  },
});

Type Safety

RISE Wallet provides comprehensive TypeScript types:

import type { PermissionsRequest } from "rise-wallet/viem";

// All actions are fully typed
const permissions: PermissionsRequest = {
  key: { publicKey: "...", type: "p256" },
  expiry: 123456789,
  permissions: {
    calls: [{
      to: "0x..." as const,
      signature: "0x..." as const,
    }],
  },
};

Error Handling

Handle Viem errors appropriately:

import { BaseError } from "viem";

try {
  await Actions.sendTransaction(client, { ... });
} catch (error) {
  if (error instanceof BaseError) {
    console.error("Error name:", error.name);
    console.error("Error details:", error.details);
    console.error("Error version:", error.version);
  }
}

Best Practices

1. Use Action Modules

Import actions from their respective modules for better tree-shaking:

// Good
import { sendTransaction } from "viem/actions";
import { connect } from "rise-wallet/viem/WalletActions";

// Less optimal
import * as Actions from "viem/actions";

2. Handle Account State

Always check account connection before operations:

const accounts = await client.request({ method: "eth_accounts" });
if (!accounts.length) {
  // Request connection first
  await WalletActions.connect(client);
}

3. Optimize Gas

Use Viem's gas utilities:

const gasPrice = await client.getGasPrice();
const gasEstimate = await client.estimateGas({
  to: "0x...",
  data: "0x...",
});

Examples