CAIP-345: Wallet Service URL property

Author Chris Smith
Discussions-To https://github.com/ChainAgnostic/CAIPs/discussions/345
Status Draft
Type Standard
Created 2025-02-17
Updated 2025-06-24
Requires 25

Simple Summary

Handling of wallet JSON-RPC requests by wallet-provided HTTP endpoint.

Abstract

This proposal defines the wallet service property, caip345, for use in CAIP-25. Wallets set this to indicate that certain methods, instead of being handled by the CAIP-25 session, will instead be sent to a JSON-RPC HTTP endpoint. Compatible apps that support this will be able to call these RPC methods, without interactivity with the actual wallet application or user.

Motivation

It is sub-optimal UX to redirect the user to their wallet in order to handle RPC requests that are non-actionable to them. This is especially relevant for protocols such as WalletConnect which is used in more distributed environments such as mobile wallets or custodial solutions. In these contexts, actioning a wallet RPC request can involve significant effort.

Examples of non-actionable wallet requests include:

By defining a way for wallets to send requests to an out-of-band endpoint, the requests can be satisfied without needing the wallet app to be open.

Specification

Wallet Service

A “wallet service” is a JSON-RPC-compatible HTTP endpoint that can be used to satisfy certain wallet RPC methods. This service may be developed, hosted, and maintained by the same organization developing the wallet app, or by a third-party. It is up to the wallet to determine what server should be responsible for handling wallet RPCs.

The wallet service can be specified as a URL, and a list of JSON-RPC methods for which to use the URL. methods SHOULD NOT be empty. The endpoint MUST be JSON-RPC compatible and support POST requests.

type WalletService = {
    methods: string[],
    url: string,
};

Wallets MAY provide query params as part of the URL. These params could be useful for many things such as providing an authentication token, connection ID, identifying the chain being used, or providing other necessary details to fulfil the request.

The endpoint SHOULD enable CORS (Cross-Origin-Resource-Sharing) to allow arbitrary app domains to access the endpoint.

The wallet service MAY respond with a Cache-Control header, indicating the cacheability of the response. Apps SHOULD set the JSON-RPC id field to 0, increasing the chance of a cache-hit.

Wallets MUST NOT set multiple wallet service entries for the same method. Apps SHOULD NOT attempt to recover from multiple or conflicting wallet service URLs, but MAY use the first URL available for the method as a convenience for implementation.

Apps SHOULD use the wallet service when available for a method, instead of calling the wallet directly.

Here is an example implementation:

const jsonRpc = { ... };

const handler = walletService.find(s => s.methods.includes(jsonRpc.method));
if (handler) {
    jsonRpc.id = 0; // optional
    return fetch(handler.url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(jsonRpc)
    });
} else {
    // fallback to sending directly to wallet
}

Usage in CAIP-25

The caip345 property can be used in both sessionProperties and scopedProperties, depending on what scopes the methods are supported on.

// scoped or session properties
type Caip25Properties = {
    caip345: WalletService[],
    [key: string]: any, // other properties
};

If a method is included in the caip345 property, then the same method MUST be included in the CAIP-25 session for the same scopes. Wallets MUST implement fallback handling for all wallet service methods, in case the app does not implement this CAIP.

Below is an unverified, non-normative, example for wallet_getAssets which is only supported in the eip155 scope:

"scopedProperties": {
    "eip155": {
        "caip345": [{
            "methods": ["wallet_getAssets"],
            "url": "https://wallet-service.example.com/rpc"
        }],
    }
}

Below is an unverifed, non-normative, example for ERC-7836, which is also only valid in the eip155 scope:

"scopedProperties": {
    "eip155": {
        "caip345": [{
            "methods": ["wallet_prepareCalls", "wallet_sendPreparedCalls"],
            "url": "https://wallet-service.example.com/rpc"
        }],
    }
}

Rationale

Support for multiple wallet service URLs

Supporting an array of wallet services increases flexibility. Allowing different URLs (servers or parameters) to be used for different methods or use cases.

There must still be 1 canonical wallet service URL for a given method (if available at all).

Different wallet service depending on account

There was consideration for being able to specify different wallet service URLs for different accounts. However, this would not make sense in the context of CAIP-25 because there is no mechanism to scope the methods to particular accounts. If this CAIP provided a way to scope the methods to particular accounts, the app may still try to send the method requests for non-listed accounts directly to the wallet.

Map methods to wallet services, instead of methods in array

There was consideration for defining the standard to have a separate wallet service URL for every single method. However, this would cause excessive bandwidth consumption if the same URL were to be used for multiple methods which we think is the more likely case.

Not supporting custom headers

Specifying custom headers to use in the wallet service request is not supported. This is because in browsers, custom headers will create pre-flight OPTIONS requests, increasing bandwidth and server load. Instead, the necessary parameters should be passed via query params.

Web apps using connect-src in Content-Security-Policy

Many web apps specify connect-src in their CSP which prevents the app and its libraries from connecting to URLs that aren’t pre-specified. Since each wallet may use their own wallet service hosted on unique origins, it’s not possible/advisable to dynamically set the connect-src value as-necessary.

However to support this use case, such apps can consume a minimal proxy service (such as a server function) which will forward the wallet RPC request to the destination wallet service. This proxy service is known by the app, and has a fixed origin URL, which allows placing it in connect-src. The proxy service could be implemented by the app, or by a third-party.

The mechanism by which this proxy service is implemented or consumed is outside the scope of this CAIP.

Test Cases

Valid wallet service:

{
    "methods": ["wallet_prepareCalls", "wallet_sendPreparedCalls"],
    "url": "https://wallet-service.example.com/rpc"
}

Invalid wallet service:

{
    "url": "https://wallet-service.example.com/rpc"
}

Security Considerations

The wallet service would bear the security responsibility of responding to these wallet RPC requests. Wallets should make informed decisions about which providers they use for this.

Privacy Considerations

Similarly, the wallet service would have visibility into the wallet RPC requests being sent to it.

Backwards Compatibility

This new property is fully backwards-compatible with CAIP-25.

As also mentioned above, wallets MUST implement fallback handling for all wallet service methods, in case the app does not implement this CAIP.

References

  • CAIP-25 is where this property is used

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Chris Smith, "CAIP-345: Wallet Service URL property [DRAFT]," Chain Agnostic Improvement Proposals, no. 345, February 2025. [Online serial]. Available: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-345.md