Definition of a Modern Stacks Wallet Interface Standard
By aryzing, janniks, kyranjamie, m-aboelenein
- Idea
- Draft
- Accepted
- CAB vote
- Recommended
- Activation
- Ratified
Abstract
This SIP proposes common RPC methods to use for the Stacks blockchain's "Connect" and "Auth" systems. The goal is to replace the current Connect interface, primarily used by web applications to connect with browser extensions and mobile apps with a more straightforward protocol. This proposal's goal is to standardize JSON compatible interfaces for use with wallet interfaces.
Introduction
The current Connect system[^15], which has existed for several years, is primarily utilized by web applications for interfacing with wallets. However, many aspects of the existing "Connect" and "Auth" libraries are no longer required, leading to unnecessary complexity (e.g., wrapping RPC payloads in jsontokens) and lack of clear definitions (e.g., undefined serialization for non-JSON compatible data structures) in wallet connectivity.
Recent attempts[^21][^22][^23][^24][^25][^26] to standardize the interface have sparked valuable discussions but have not culminated in a ratified standard, largely due to the stable state of the existing system. This SIP aims to address these issues by adopting the WBIPs standards[^11], which offer a more suitable RPC-style interface for modern web applications. The simplified protocol will allow integration without heavy dependencies (like Auth) and provide a more extendable interface for wallets.
Additionally, this SIP is motivated by the increased traffic of Ordinal inscriptions on Bitcoin and upcoming integrations like sBTC that require standardized Stacks and Bitcoin transaction signing capabilities in wallets. The community has recognized the need for a more unified approach to wallet connectivity (e.g. Bitcoin and PSBTs for previously Stacks-only wallets). By adopting the new standard, we aim to align the community towards a common and modern protocol for wallet interaction in web applications. Importantly, the decision to use an existing standard (rather than designing a new one or reworking Auth) is intentional — to avoid further division or split ownership within the community.
There was an attempt to re-use existing standards/protocols from other ecosystems via the WBIPs working group[^11][^26] — but no consensus was found that was a perfect fit or had enough traction for the larger layer-2 ecosystem. So this SIP aims to capture the important features for the Stacks ecosystem, with a focus on extensibility.
Specification
The proposed changes are listed as follows:
- Use JSON-RPC 2.0 as the underlying protocol for wallet interaction.
- Specify compatible RPC methods and payloads for wallet interaction.
- Define a recommended provider object and its discovery mechanism.
Note: The methods can be used via the mentioned browser object (i.e., via the
window.WalletProvider.requestmethod), but also through similar interfaces like WalletConnect.
Notes on Serialization
To ensure serializability, consider these notes:
- Enums are serialized as human-readable strings.
- BigInts are serialized as numbers, strings, or anything that can be parsed by the JavaScript BigInt constructor.
- Bytes are serialized as hex-encoded strings (without a 0x prefix).
- Predefined formats from previous SIPs are used where applicable.
- Addresses are serialized as Stacks c32-encoded strings.
- Clarity values, post-conditions, and transactions may be serialized to bytes (defined by SIP-005) and used as hex-encoded strings.
Methods
This section defines the available methods, their parameters, and result structure. Parameters should be considered only as recommendations for the wallet, and the user/wallet may choose to ignore or override them.
Note: Optional params are marked with a
?.
Methods can be namespaced under stx_ if used in more generic settings and other more Ethereum inspired domains.
In other cases (e.g. WalletConnect), the namespace may already be given by metadata (e.g. a chainId field) and can be omitted.
General parameters (for transaction methods)
The following definitions can be used in the transaction methods.
Note: The types
PostConditionandClarityValueare used in the below definitions. They are defined later in this document under JSON Representations.
Parameter properties
address?:stringaddress, Stacks c32-encoded, defaults to wallets current addressnetwork?:'mainnet' | 'testnet' | 'regtest' | 'devnet' | 'mocknet' | NetworkId- where
NetworkId extends stringand can be a network identifier, which already exists in the wallet (e.g. a URL or network name)
- where
fee?:number | stringBigInt constructor compatible valuenonce?:number | stringBigInt constructor compatible valuepostConditions?:PostCondition[], defaults to[]- where
PostConditionisstring | objecthex-encoded or JSON representation
- where
postConditionMode?:'allow' | 'deny'sponsored?:boolean, defaults tofalsebroadcast?: boolean whether transaction is to be broadcast, defaults totrueremovedattachment?removedappDetailsremovedonFinishremovedonCancel
Method stx_transferStx
Comment: This method doesn't take post-conditions.
Parameter properties
recipient:stringaddress, Stacks c32-encodedamount:number | stringBigInt constructor compatible valuememo?:string, defaults to''
Result properties
txid:stringhex-encodedtransaction:stringhex-encoded raw transaction
Method stx_transferSip10Ft
Parameter properties
recipient:stringaddress, Stacks c32-encodedasset:stringaddress, Stacks c32-encoded, with contract name suffix and asset suffixamount:number | stringBigInt constructor compatible value
Result properties
txid:stringhex-encodedtransaction:stringhex-encoded raw transaction
Method stx_transferSip9Nft
Parameter properties
recipient:stringaddress, Stacks c32-encodedasset:stringaddress, Stacks c32-encoded, with contract name suffix and asset suffixassetId:ClarityValue
where
ClarityValue:string | objecthex-encoded or JSON representation
Result properties
txid:stringhex-encodedtransaction:stringhex-encoded raw transaction
Method stx_callContract
Parameter properties
contract:string.stringaddress with contract name suffix, Stacks c32-encodedfunctionName:stringfunctionArgs:ClarityValue[], defaults to[]- where
ClarityValueisstring | objecthex-encoded or JSON representation
- where
Result properties
txid:stringhex-encodedtransaction:stringhex-encoded raw transaction
Method stx_deployContract
Parameter properties
name:stringclarityCode:stringClarity contract codeclarityVersion?:number
Result properties
txid:stringhex-encodedtransaction:stringhex-encoded raw transaction
Method stx_signTransaction
Parameter properties
transaction:stringhex-encoded raw transactionbroadcast?:booleanwhether transaction is to be broadcast, defaults tofalse
Result properties
transaction:stringhex-encoded raw transaction (signed)
Method stx_signMessage
Parameter properties
message:string
Result properties
signature:stringhex-encodedpublicKey:stringhex-encoded
Method stx_signStructuredMessage
Parameter properties
message:ClarityValue- where
ClarityValueisstring | objecthex-encoded or JSON representation
- where
domain:ClarityTupleValue- where
ClarityTupleValue(defined by SIP-018) is a Clarity "tuple" asstring | objecthex-encoded or JSON representation
- where
Result properties
signature:stringhex-encodedpublicKey:stringhex-encoded
Method stx_getAddresses
Note: This method can be used similarly to legacy "connect" methods, where the first account selection also acts as an approval of the site/domain.
Result properties
addresses:{}[]address:stringaddress, Stacks c32-encodedpublicKey:stringhex-encodednetwork?:stringnetwork identifier
Method stx_getAccounts
Comment: This method is similar to
stx_getAddresses. It was added to provide better backwards compatibility for applications using Gaia.
Note: This method can be used similarly to legacy "connect" methods, where the first account selection also acts as an approval of the site/domain.
Result properties
accounts:{}[]address:stringaddress, Stacks c32-encodedpublicKey:stringhex-encodedgaiaHubUrl:stringURLgaiaAppKey:stringhex-encoded
Method stx_getNetworks
Result properties
active:stringnetwork identifiernetworks:{}[]id:stringchainId:numbertransactionVersion:number
Method stx_updateProfile
Parameter properties
profile:objectSchema.org Person object[^13]
Result properties
profile:objectupdated Schema.org Person object[^13]
Listeners
In addition to the request interface, event listeners may be provided via the .listen method.
provider.listen(event: string, listener: (...args: any[]) => void): Function
provider.listenshould return a "unlisten" function, which can be called to remove the listener.
The event name should be closer to nouns than verbs and doesn't use the on prefix from DOM naming conventions.
Event stx_accountChange
listener: (accounts: {}[]) => void
accountsas defined above instx_getAccounts. The first account is considered the default account (and may be the only "active" account in a wallet).
Event stx_networkChange
listener: (data: GetNetworksResult) => void- where
GetNetworksResultmatches thestx_getNetworksresult type
- where
This event may be emitted when the active network changes and on first load of the wallet/site.
Error Codes
Errors thrown by request methods should match existing JSON-RPC 2.0 error codes. This way, the user or an intermediary library can handle them in a standardized way.
Standard JSON-RPC Error Codes
- -32700: Parse Error - Invalid JSON received by server while parsing
- -32600: Invalid Request - Invalid Request object
- -32601: Method Not Found - Method not found/available
- -32602: Invalid Params - Invalid method parameters
- -32603: Internal Error - Internal JSON-RPC error
Implementation-Defined Wallet Error Codes
Implementation-defined wallet errors range from -32099 to -32000.
- -32000: User Rejection - User rejected the request
- -32001: Method Address Mismatch - Address mismatch for the requested method
- -32002: Method Access Denied - Access denied for the requested method
Custom Error Codes
Custom error codes outside the JSON-RPC error code range using the -31099 to -31000 range.
- -31000: Unknown Error - Unknown external error (does not originate from the wallet)
- -31001: User Canceled - User canceled the request (may not originate from the wallet)
JSON Representations
For historical reasons, a Stacks.js internal representation, based on the Stacks core code, has been used in serialized payloads to wallets. These representations are not human-readable and thus make debugging difficult. A better solution would be to rely on string literal enumeration, rather than magic values, which need additional lookups. Relying on solely on hex-encoding also poses difficulties when building Stacks enabled web applications.
Clarity values
Clarity values should be represented in the following format for use in Stacks.js and JSON compatible environments:
Comment: For encoding larger than JS
Numberbig integers,stringis used withbigintany compatible values.
ClarityValueisstring | objecthex-encoded or JSON representation of any of the below Clarity values.
0x00 int
{
type: 'int',
value: string // `bigint` compatible
}
0x01 uint
{
type: 'uint',
value: string // `bigint` compatible
}
0x02 buffer
{
type: 'buffer',
value: string // hex-encoded string
}
0x03 bool true
{
type: 'true',
}
0x04 bool false
{
type: 'false',
}
0x05 address (aka "standard principal")
{
type: 'address',
value: string // Stacks c32-encoded
}
0x06 contract (aka "contract principal")
{
type: 'contract',
value: `${string}.${string}` // Stacks c32-encoded, with contract name suffix
}
0x07 ok (aka "response ok")
{
type: 'ok',
value: ClarityValue // Clarity value
}
0x08 err (aka "response err")
{
type: 'err',
value: ClarityValue // Clarity value
}
0x09 none (aka "optional none")
{
type: 'none',
}
0x0a some (aka "optional some")
{
type: 'some',
value: ClarityValue // Clarity value
}
0x0b list
{
type: 'list',
value: ClarityValue[] // Array of Clarity values
}
0x0c tuple
{
type: 'tuple',
value: Record<string, ClarityValue> // Record of Clarity values
}
0x0d ascii
{
type: 'ascii',
value: string // ASCII-compatible string
}
0x0e utf8
{
type: 'utf8',
value: string
}
Post-conditions
0x00 STX
{
type: 'stx-postcondition',
address: 'origin' | string | `${string}.${string}`, // Stacks c32-encoded, with optional contract name suffix
condition: 'eq' | 'gt' | 'gte' | 'lt' | 'lte',
amount: string // `bigint` compatible, amount in micro-STX
}
0x01 Fungible token
{
type: 'ft-postcondition',
address: 'origin' | string | `${string}.${string}`, // Stacks c32-encoded, with optional contract name suffix
condition: 'eq' | 'gt' | 'gte' | 'lt' | 'lte',
asset: `${string}.${string}::${string}` // Stacks c32-encoded address, with contract name suffix, with asset suffix
amount: string // `bigint` compatible, amount in lowest integer denomination of fungible token
}
0x02 Non-fungible token
{
type: 'nft-postcondition',
address: 'origin' | string | `${string}.${string}`, // Stacks c32-encoded, with optional contract name suffix
condition: 'sent' | 'not-sent',
asset: `${string}.${string}::${string}` // address with contract name suffix with asset suffix, Stacks c32-encoded
assetId: ClarityValue, // Clarity value
}
Test vectors
Listed below are some examples of the potentially unclear representations:
u12={ type: "uint", value: "12" }0xbeaf={ type: "ascii", value: "hello there" }"hello there"={ type: "ascii", value: "hello there" }(list 4 8)={ type: "list", value: [ { type: "int", value: "4" }, { type: "int", value: "8" }, ] }(err u4)={ type: "err", value: { type: "uint", value: "4" }, }- "sends more than 10000 uSTX" =
{ type: "stx-postcondition", address: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6", amount: "10000", condition: "gt" } - "does not send the
12TKN non-fungible token" ={ type: "ntf-postcondition", address: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.vault" asset: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.tokencoin::tkn", assetId: { type: "uint", value: "12" } condition: "not-sent" }
Provider registration
Applications may hardcode known wallets, i.e. store their provider name. Libraries may use discovery mechanisms like the WBIP-004[^3] standard or Wallet Standard[^14].
Using a shared global object between multiple wallets is discouraged.
The historical window.StacksProvider object has led to issues in the past, where overwriting the same object has led to undefined wallet registration behavior.
Reference Implementations
The following git tags provide examples of how the new standard can be implemented in TypeScript, as well as validation libraries:
Backwards Compatibility
The implementation of this proposal is not necessarily backward compatible.
Notably, app-specific private keys for domains have been removed from the connect protocol, but can still be accessed via the gaiaAppKey field in stx_getAccounts.
Wallets implementing the new standard may maintain the previous system to support legacy applications during a transition period or indefinitely. Existing applications using the current Auth system should continue to operate, but immediate changes are recommended once this SIP is ratified.
Activation
This SIP is considered Ratified after at least two wallet providers with significant user adoption (> 10,000 monthly active users) have implemented and launched the new standard.
Once wallets have implemented the new standard, tooling (e.g. Stacks Connect[^15]) can be updated to support the new standard as well. This SIP is not consensus breaking, thus the timeline for activation is not tied to Stacks releases.
Related Work
This SIP is designed as a replacement for the existing Connect system[^15], due to the issues mentioned above.
The standard builds on top of the following work: the webbtc .request standard[^10], Wallet Standard[^14], and WBIPs[^11].
This SIP is meant to be compatible with various use cases and is meant as a formal specification to unify and drive forward the wallet RPC ecosystem.
Auth
One of the core libraries referenced in this proposal is often called "Auth", which is packaged within the existing Stacks "Connect" libraries[^15], as well as separately as @stacks/auth on npm.
This Auth system is a legacy Blockstack library, which was never specified in a SIP.
It relies on wrapping RPC payloads in jsontokens.
While Auth served as an essential building block for the legacy Blockstack system, its assumptions are no longer strictly necessary. By using a simpler JSON-RPC 2.0–based approach for wallet connectivity, this SIP removes the need for Auth.
Auth
One of the core libraries referenced in this proposal is often called "Auth", which is packaged within the existing Stacks "Connect" libraries[^15], as well as separately as @stacks/auth on npm.
This Auth system is a legacy Blockstack library, which was never specified in a SIP.
It relies on wrapping RPC payloads in jsontokens.
While Auth served as an essential building block for the legacy Blockstack system, its assumptions are no longer strictly necessary. By using a simpler JSON-RPC 2.0–based approach for wallet connectivity, this SIP removes the need for Auth.
Appendix
<!-- Discussions --> <!-- References -->WBIPs documents partially worked on in the working group with Leather, Xverse, and others.
zadao.ai