#Abstract
This standard aims to enhance developer experiences by standardizing an interface for web wallets to use when interacting with other codebases.
#Motivation
There are numerous Stellar wallets in the ecosystem with this number expected to grow as the ecosystem does. Wallets expose an API to allow dapps to call methods that perform such common tasks as transmitting a wallet's address. An issue is that wallets all use slightly different API's to perform similar functions, with different method names and call signatures as well as different return values. Developers have a motivation to integrate with as many as possible, but currently, they must create abstractions to handle every wallet they wish to connect to. This proposal presents a standard API interface for all wallets to implement. If all wallets have the same interface, it removes the need for developers to create abstractions for every individual wallet they want to integrate with. Developers can safely assume a wallet uses this agreed-upon interface, creating simpler code paths that easily connect to current wallets while also simplifying the onboarding of new wallets.
#Specification
The standard web wallet interface provides a structure for common methods that wallets are expected to offer. In addition, the interface defines common error message shapes to ensure wallets adhere to informative and usable error messaging. Messages are designed to be surfaced directly to the user, so they should provide a readable description of what went wrong.
#Wallet Interface Format
Wallets will employ an API using the following format:
interface Error {
message: string, // general description message returned to the client app
code: number, // unique error code
ext?: Array<string> // optional extended details
}
{
getAddress: () =>
Promise<{
address: string;
} & error?: Error>,
signTransaction: (
xdr: string,
opts?: {
networkPassphrase?: string,
address?: string;
submit?: boolean;
submitUrl?: string;
}) =>
Promise<{
signedTxXdr: string;
signerAddress: string;
} & error?: Error>,
signAuthEntry: (
authEntry: string,
opts?: {
networkPassphrase?: string,
address?: string
}) =>
Promise<{
signedAuthEntry: string;
signerAddress: string;
} & error?: Error>,
signMessage: (
message: string,
opts?: {
networkPassphrase?: string,
address?: string
}) =>
Promise<{
signedMessage: string;
signerAddress: string;
} & error?: Error>,
getNetwork: () =>
Promise<{
network: string,
networkPassphrase: string
} & error?: Error>
}
In addition, this proposal has a standard for error interfaces.
The errors will return a specific code depending on the type of error that the wallet is encountering. Codes map as follows:
-1: internal wallet error, likely caused by the wallet logic itself
-2: external service (Horizon, RPC, etc.) returned an error
-3: client app request is invalid (wrong parameters, invalid transaction XDR, etc.)
-4: user rejected the request, client app should not try to retry the request
Alongside these error codes, the wallet will include a message key that maps
to a custom error string that the wallet can define. This string will be a
human readable message that informs the user of what went wrong.
Wallets may also include an optional ext field which is an array of strings
that may provide some specific information about the error.
Below are examples of different kinds of errors a wallet may surface:
// an internal error occurred in the wallet. For example, a JS runtime error
{
message: 'The wallet encountered an internal error. Please try again or contact the wallet if the problem persists.',
code: -1
},
// an error occurred with an external service. For example, Horizon returned an error
{
message: 'An error occurred with Horizon. Please try again.',
code: -2,
ext: ['Operation 2: Invalid "amount" value.', "Operation 5: Asset issuer is required."] // malformed tx error example
},
// Either the dapp or the user passed a malformed request to the wallet. For example, a user didn't pass a valid XDR to `signTransaction`
{
message: 'Request is invalid. Please check the details and try again.',
code: -3,
ext: ['Invalid transaction XDR'] // example error
},
// The wallet received the request from the client, but the user decided to decline.
rejected: {
message: 'The user rejected this request.',
code: -4
}
#Methods
#getAddress
getAddress will provide the public key (G...) the wallet is signing for. This
may be the account loaded in the wallet, or it may be a different account that
the wallet is the signer for.
#signTransaction
signTransaction will take an XDR provided by the requester and apply a
signature to it before returning a base64-encoded string XDR-encoded
Transaction Envelope
with
Decorated Signatures
and the signer address back to the requester. The first parameter is the XDR
string while the second parameter is a set of options. Options allow a user to
specify what network they intend the transaction to be signed on. They can do
so by passing the network's passphrase as networkPassphrase. Options also
allow a wallet to specify which account they're seeking a signature for as
address. If a wallet holds numerous addresses, it can use this param to
ensure it is signing with the intended address. Options also allow a user to
set a submit flag, which will allow a user to sign a transaction and
immediately submit it to a network. If a user would like to configure the URL
of the network to which they would like to submit, they can do so by setting
submitUrl.
#signAuthEntry
Similar to its transaction counterpart, signAuthEntry will take an
authorization entry preimage
provided by the requester and apply a signature to it before returning a signed
hash of the same authorization entry and the signer address back to the
requester. The first parameter is the XDR string while the second parameter is
a set of options. Options allow a user to specify what network they intend the
transaction to be signed on. They can do so by passing the network's passphrase
as networkPassphrase. Options also allow a wallet to specify which account
they're seeking a signature for as address. If a wallet holds numerous
addresses, it can use this param to ensure it is signing with the intended
address.
#signMessage
signMessage is similar to its transaction and auth entry counterparts in that
it will take a payload provided by the requester and apply a signature to it
before returning the HEX-encoded message derived from the public key and
original message as well as the signer address back to the requester. This
method is different in that signMessage accepts an arbitrary string rather
than a transaction or auth entry. This can be used to implement
identity/ownership verification. The first parameter is the XDR string while
the second parameter is a set of options. Options allow a user to specify what
network they intend the transaction to be signed on. They can do so by passing
the network's passphrase as networkPassphrase. Options also allow a wallet to
specify which account they're seeking a signature for as address. If a wallet
holds numerous addresses, it can use this param to ensure it is signing with
the intended address.
#getNetwork
getNetwork provides details about the network that the wallet is currently
configured to. This allows a dapp to confirm that they are building a
transaction for the correct network.
#Errors
Error messaging provides clear errors to the requester. The message field
provides an informative, human readable string that a UI could surface to the
end user. This message should include actions a user can take to remedy the
situation. The 4 types of code key delineate between possible errors: those
caused by an internal wallet error, an error caused by external service (for
ex: Horizon or Soroban RPC), invalid user input (for ex: a malformed XDR), and
an error caused by the user rejecting the action requested. Finally, the ext
field is an optional value that can provide more context for the error, if
needed. The wallet is responsible for localizing the message and ext fields
to be useful for their users.