Skip to main content
Rise B2B API is designed for seamless integration with Web3 wallets and smart contract standards. This guide covers wallet integration and EIP-712 typed data signing for secure blockchain operations using the official Rise SDK.

Secure

EIP-712 typed data signing for enhanced security

Efficient

Gas-optimized transactions with structured data

Compatible

Works with MetaMask, WalletConnect, and other wallets

User-Friendly

Human-readable messages in wallet interfaces

SDK Installation

First, install the Rise SDK:
npm install @riseworks/sdk

Supported Wallets

Rise B2B API supports all major Web3 wallets:

MetaMask

Most popular Ethereum wallet

WalletConnect

Multi-wallet connection protocol

Coinbase Wallet

Coinbase’s Web3 wallet

Rainbow

Beautiful mobile wallet

Trust Wallet

Binance’s mobile wallet

Any EIP-1193

Compatible with any EIP-1193 wallet

Getting Started

Initialize the SDK

const { RiseApiClient } = require('@riseworks/sdk');
require('dotenv').config();

// Initialize with Rise ID and private key (recommended for Web3 operations)
const client = new RiseApiClient({
  environment: 'prod',
  riseIdAuth: {
    riseId: process.env.RISE_ID,
    privateKey: process.env.PRIVATE_KEY
  }
});

// Alternative: Initialize with JWT token
const jwtClient = new RiseApiClient({
  environment: 'prod',
  jwtToken: process.env.JWT_TOKEN
});

EIP-712 Typed Data Signing

Many Rise operations (payments, team management, invites) require EIP-712 typed data signing for enhanced security and gas efficiency. The SDK handles this automatically for most operations.

What is EIP-712 Typed Data?

EIP-712 is a standard for typed data signing that provides:
  • Human-readable messages in wallet interfaces
  • Type safety to prevent signature replay attacks
  • Gas efficiency compared to raw message signing
  • Better security through structured data validation

Automatic Typed Data Signing

The SDK automatically handles typed data signing for most operations:
// Automatic payment with signing
const payment = await client.payments.sendPayment({
  from: 'te-abc123def456',
  to: [
    {
      to: 'us-xyz789abc123',
      amount_cents: 10000,
      currency_symbol: 'USD',
      invoice_description: 'Salary payment for January 2024'
    }
  ],
  pay_now: true,
  network: 'arbitrum'
});

console.log('Payment executed:', payment.data);
What the SDK does automatically:
  1. Generates typed data for EIP-712 signing
  2. Signs the typed data using your private key
  3. Submits the signed transaction to the Rise API
  4. Handles authentication and JWT token management
  5. Provides error handling and retry logic

Manual Typed Data Signing

For more control over the signing process, you can handle the signing manually:
// Initialize SDK (private key not required for manual flow)
const client = new RiseApiClient({
  environment: 'prod',
  jwtToken: process.env.JWT_TOKEN  // JWT token is sufficient for manual flow
});

// Step 1: Get typed data for signing
const typedDataResponse = await client.payments.getPaymentTypedData({
  from: 'te-abc123def456',
  to: [
    {
      to: 'us-xyz789abc123',
      amount_cents: 10000,
      currency_symbol: 'USD',
      invoice_description: 'Salary payment for January 2024'
    }
  ],
  pay_now: true,
  network: 'arbitrum'
});

console.log('Typed data received:', typedDataResponse.data);

// Step 2: Sign the typed data (if you want custom signing logic)
const { ethers } = require('ethers');
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
const signature = await wallet.signTypedData(
  typedDataResponse.data.domain,
  typedDataResponse.data.types,
  typedDataResponse.data.typed_data
);

// Step 3: Execute with signature
const executeResponse = await client.payments.executePaymentWithSignedData({
  from: 'te-abc123def456',
  to: [
    {
      to: 'us-xyz789abc123',
      amount_cents: 10000,
      currency_symbol: 'USD',
      invoice_description: 'Salary payment for January 2024'
    }
  ],
  pay_now: true,
  network: 'arbitrum',
  signer: wallet.address,
  typed_data: typedDataResponse.data.typed_data,
  signature: signature
});

console.log('Payment executed:', executeResponse.data);
What the manual flow provides:
  1. Custom signing logic - You control when and how to sign
  2. Typed data inspection - You can examine the data before signing
  3. Signature verification - You can verify the signature before submission
  4. Error handling control - Custom error handling for each step
  5. Integration flexibility - Easier integration with existing signing workflows

When to Use Typed Data Signing

Typed data signing is required for the following Rise operations:

Payments

Creating and executing payments

Team Management

Adding/removing team members

Invites

Sending team invitations

Settings

Updating team settings

Permissions

Managing role permissions

Withdrawals

Processing withdrawals

Complete Integration Example

Here’s a complete example showing how to integrate Web3 operations with Rise API using the SDK:
const { RiseApiClient } = require('@riseworks/sdk');
require('dotenv').config();

class RiseWeb3Integration {
  constructor() {
    this.client = new RiseApiClient({
      environment: 'prod',
      riseIdAuth: {
        riseId: process.env.RISE_ID,
        privateKey: process.env.PRIVATE_KEY
      }
    });
  }

  // Automatic payment with signing
  async createPayment(teamNanoid, recipients, payNow = true) {
    try {
      const payment = await this.client.payments.sendPayment({
        from: teamNanoid,
        to: recipients,
        pay_now: payNow,
        network: 'arbitrum'
      });

      console.log('Payment executed:', payment.data);
      return payment.data;
    } catch (error) {
      console.error('Payment failed:', error.message);
      throw error;
    }
  }

  // Automatic manager invite with signing
  async createManagerInvite(teamNanoid, emails, role) {
    try {
      const managerInvite = await this.client.invites.sendManagerInvite({
        emails: emails,
        role: role,
        nanoid: teamNanoid
      });

      console.log('Manager invite sent:', managerInvite.data);
      return managerInvite.data;
    } catch (error) {
      console.error('Manager invite failed:', error.message);
      throw error;
    }
  }

  // Automatic withdrawal with signing
  async processWithdrawal(accountNanoid, amount, currency) {
    try {
      const withdrawal = await this.client.withdraw.sendWithdraw(
        { account_nanoid: accountNanoid },
        {
          amount_cents: amount,
          currency_symbol: currency
        }
      );

      console.log('Withdrawal processed:', withdrawal.data);
      return withdrawal.data;
    } catch (error) {
      console.error('Withdrawal failed:', error.message);
      throw error;
    }
  }

  // Manual payment with custom signing
  async createManualPayment(teamNanoid, recipients, payNow = true) {
    try {
      // Step 1: Get typed data
      const typedDataResponse = await this.client.payments.getPaymentTypedData({
        from: teamNanoid,
        to: recipients,
        pay_now: payNow,
        network: 'arbitrum'
      });

      console.log('Typed data received:', typedDataResponse.data);

      // Step 2: Sign the typed data
      const { ethers } = require('ethers');
      const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
      const signature = await wallet.signTypedData(
        typedDataResponse.data.domain,
        typedDataResponse.data.types,
        typedDataResponse.data.typed_data
      );

      // Step 3: Execute with signature
      const executeResponse = await this.client.payments.executePaymentWithSignedData({
        from: teamNanoid,
        to: recipients,
        pay_now: payNow,
        network: 'arbitrum',
        signer: wallet.address,
        typed_data: typedDataResponse.data.typed_data,
        signature: signature
      });

      console.log('Manual payment executed:', executeResponse.data);
      return executeResponse.data;
    } catch (error) {
      console.error('Manual payment failed:', error.message);
      throw error;
    }
  }

  // Generic typed data signing utility
  async signTypedData(typedData, privateKey) {
    const { ethers } = require('ethers');
    const wallet = new ethers.Wallet(privateKey);

    const signature = await wallet.signTypedData(
      typedData.domain,
      typedData.types,
      typedData.typed_data
    );

    return signature;
  }
}

// Usage example
const web3Integration = new RiseWeb3Integration();

async function main() {
  try {
    // Example: Create automatic payment
    const recipients = [
      {
        to: 'us-jRxg2LRL54DJ',
        amount_cents: 300,
        currency_symbol: 'USD',
        invoice_description: 'Salary payment'
      },
      {
        to: 'us-d6JHBF2kuZjE',
        amount_cents: 700,
        currency_symbol: 'USD',
        invoice_description: 'Bonus payment'
      }
    ];

    const payment = await web3Integration.createPayment(
      'te-abc123def456',
      recipients,
      true // pay immediately
    );

    // Example: Create manager invite
    const managerInvite = await web3Integration.createManagerInvite(
      'te-abc123def456',
      ['admin@company.com'],
      'team_admin'
    );

    // Example: Process withdrawal
    const withdrawal = await web3Integration.processWithdrawal(
      'ac-xyz789abc123',
      5000, // $50.00
      'USD'
    );

    // Example: Manual payment with custom signing
    const manualPayment = await web3Integration.createManualPayment(
      'te-abc123def456',
      recipients,
      true
    );

  } catch (error) {
    console.error('Web3 integration error:', error.message);
  }
}

// Run the example
if (require.main === module) {
  main();
}

Security Best Practices

Private Keys

  • Never store private keys in client-side code
  • Use environment variables for server-side operations
  • Consider hardware wallets for high-value transactions

Verification

  • Always verify signatures before submitting
  • Check contract addresses and chain IDs
  • Validate typed data structure

Environment

  • Use HTTPS for all API communications
  • Implement proper error handling
  • Log security events for auditing

Timing

  • Respect expiration times in typed data
  • Implement retry logic for failed transactions
  • Monitor transaction status

Error Handling

The SDK provides comprehensive error handling:
try {
  const payment = await client.payments.sendPayment({
    from: 'te-abc123def456',
    to: [
      {
        to: 'us-xyz789abc123',
        amount_cents: 10000,
        currency_symbol: 'USD',
        invoice_description: 'Payment'
      }
    ],
    pay_now: true,
    network: 'arbitrum'
  });
  
  console.log('Payment successful:', payment.data);
} catch (error) {
  console.error('Web3 operation error:', error.message);
  
  // Handle specific error types based on message content
  if (error.message.includes('insufficient balance')) {
    console.error('Insufficient funds for payment');
  } else if (error.message.includes('invalid signature')) {
    console.error('Signature verification failed');
  } else if (error.message.includes('expired')) {
    console.error('Typed data has expired');
  } else if (error.message.includes('wrong network')) {
    console.error('Incorrect network connected');
  } else {
    console.error('Web3 operation failed:', error.message);
  }
}
Common Web3 errors:
Error MessageDescriptionSolution
insufficient balanceNot enough funds for transactionEnsure wallet has sufficient balance
invalid signatureSignature verification failedEnsure the correct wallet is signing
expiredTyped data has expiredRequest fresh typed data from API
wrong networkIncorrect network connectedSwitch to the correct network
invalid typed dataInvalid typed data structureCheck typed data format

Testing Web3 Integration

For testing, use the staging environment:
// Staging configuration for testing
const stagingClient = new RiseApiClient({
  environment: 'stg',
  riseIdAuth: {
    riseId: process.env.RISE_ID,
    privateKey: process.env.PRIVATE_KEY
  }
});

// Test Web3 operations
const testPayment = await stagingClient.payments.sendPayment({
  from: 'te-test123',
  to: [
    {
      to: 'us-test456',
      amount_cents: 100,
      currency_symbol: 'USD',
      invoice_description: 'Test payment'
    }
  ],
  pay_now: true,
  network: 'arbitrum'
});

console.log('Test payment executed:', testPayment.data);

Environment Configuration

For the complete list of API URLs for each environment, see our Environments page.
// Staging (Arbitrum Sepolia)
const STAGING_CONFIG = {
  environment: 'stg',
  chainId: 421614,
  verifyingContract: '0x...' // Sepolia contract address
};

// Production (Arbitrum mainnet)
const PROD_CONFIG = {
  environment: 'prod',
  chainId: 42161,
  verifyingContract: '0x...' // Mainnet contract address
};

Next Steps

Need help? Check out our Payment Integration Guide for a complete example of typed data signing in action.
I