Wallet Support on Stacks


Introduction
Version 8 of @stacks/connect
unifies and simplifies the API for interacting with Stacks-compatible wallets. The familiar wallet flows from pre-version 8 (showConnect
, doContractCall
, etc.) have morphed into a more homogeneous request()
method that supports all Stacks-compatible wallets.
This guide takes stacks developers through the steps needed to upgrade their applications to support the new request flows. See the official stacks connect docs for the latest wallet compatibility guides and the reference docs for a summary of all the new connect commands.
NPM Modules
Update the supported versions of the main stacks libraries (available from searching the npm registry).
npm list @stacks/connect @stacks/network @stacks/transactions
npm install @stacks/connect@latest
npm install @stacks/transactions@latest
npm install @stacks/network@latest
npm install @stacks/encryption@latest
npm install @stacks/common@latest
Connection
Update showConnect
Our pre v8 connection method looked as follows;
export async function authenticate(callback: any) {
showConnect({
appDetails: {
name: 'My App',
icon: window?.location?.origin || '' + '/my-app-logo.svg'
},
//redirectTo: '/',
onFinish: () => {
let userData = userSession.loadUserData();
storedStacksWallet.set(userData);
if (callback) callback();
},
userSession
});
}
In v8, this becomes;
export async function authenticate(callback?: () => void) {
const opts: ConnectRequestOptions = {} as ConnectRequestOptions;
if (!isConnected()) {
const response = await connect();
console.log('Connected with addresses:', response);
}
if (callback) callback();
}
Update getAddresses
Our pre v8 code for extracting stacks and bitcoin addresses was quite a mess because it depended on which wallet the user chose xverse, leather etc to conect to our app. The new request API thankfully harmonises address lookups;
const response = await request('getAddresses');
So for example fetching the user stacks and bitcoin addresses becomes;
export function getStxAddress() {
const userData = getLocalStorage();
return userData?.addresses.stx[0].address || '???';
}
export function getBtcAddress() {
const userData = getLocalStorage();
return userData?.addresses.btc[0].address || '???';
}
you could of course use isConnected()
to check before looking up the addresses and throw en exception.
As well as a neater API the returned response data has improved structure and information but we noticed a confusing gotcha here. The data returned from;
const response = await connect({});
const addresses = [
{
symbol: 'BTC',
type: 'p2wpkh', // Native SegWit
address: 'bc1q56mcj0rxr7kpva4ahegcsq0tecppn6v4rajrn7',
derivationPath: "m/84'/0'/1'/0/0",
publicKey: '034f7d056c2783c72fd7043f9af4e8400db08644e2db7c7b482ef3b3e3791b68a6',
},
{
symbol: 'BTC',
type: 'p2tr', // Taproot
address: 'bc1peyn9qvn2wmusg2m7d29a7veya6l6vhk8jmn9r80g5s2dz9fc7ghq3ut9wq',
derivationPath: "m/86'/0'/1'/0/0",
publicKey: '02ef9f9aaa3930bdba2409f041b2764f209e01c50591c2cf91d984ac4d1db070f4',
tweakedPublicKey: 'ef9f9aaa3930bdba2409f041b2764f209e01c50591c2cf91d984ac4d1db070f4',
},
{
symbol: 'STX',
address: 'SP2F4ZBBV22RF2WYR424HKX5RDN6XRK19X1D02QRA',
publicKey: '02ecaaabe6feaca1883212fa4fb79b1789b93218b7235ca2e7d8e21f4fc4b07895',
},
];
is different from that returned from;
const userData = getLocalStorage()
which has a different structure and lacks the nice details (tweaked pubkey, derivation path) returned here;
const addresses = {
btc: [
{
symbol: 'BTC',
type: 'p2wpkh',
address: 'bc1q56mcj0rxr7kpva4ahegcsq0tecppn6v4rajrn7',
},
{
symbol: 'BTC',
type: 'p2tr',
address: 'bc1peyn9qvn2wmusg2m7d29a7veya6l6vhk8jmn9r80g5s2dz9fc7ghq3ut9wq',
},
],
stx: [
{
symbol: 'STX',
address: 'SP2F4ZBBV22RF2WYR424HKX5RDN6XRK19X1D02QRA',
},
],
};
Signatures
There are two signature schemes in Stacks for unstructured and structured data respectively.
The simpler signature scheme;
async function signMessage() {
const message = 'Hello World';
const response = await request('stx_signMessage', {
message,
});
console.log('Signature:', response.signature);
console.log('Public key:', response.publicKey);
}
signs a simple message with the private key of the users connected wallet.
On BigMarket we use structured signatures for securing communication with backend server and for batch updates in Clarity smart contracts (more on that in coming post). The upgrade involves switching out our calls to the wallets openStructuredDataSignatureRequestPopup method for the new request method;
const signature = await request('stx_signStructuredMessage', {
message: tupleCV({
message: stringAsciiCV(adminMessage.message),
timestamp: uintCV(adminMessage.timestamp),
admin: stringAsciiCV(adminMessage.admin)
}),
domain: tupleCV({
name: stringAsciiCV(getConfig().VITE_PUBLIC_APP_NAME),
version: stringAsciiCV(getConfig().VITE_PUBLIC_APP_VERSION),
'chain-id': uintCV(chainId)
})
});
Contract Calls
Our usual contract calls;
await openContractCall({
network: getStacksNetwork($configStore.VITE_NETWORK),
postConditions,
postConditionMode: PostConditionMode.Deny,
contractAddress: getDaoConfig().VITE_DOA_DEPLOYER,
contractName,
functionName: 'create-market',
functionArgs: await getArgsCV(dataHash),
onFinish: (data: any) => {
txId = data.txId;
console.log('finished contract call!', data);
onPollSubmit(txId);
},
onCancel: () => {
console.log('popup closed!');
}
});
work with version 8 but we decided to upgrade these inline with the new API (stx_callContract) while we had the hood up;
const response = await request('stx_callContract', {
contract: `ST20M5GABDT6WYJHXBT5CDH4501V1Q65242SPRMXH.pyth-oracle-v3`,
functionName: 'read-price-feed',
functionArgs: [feedId, Cl.principal('ST20M5GABDT6WYJHXBT5CDH4501V1Q65242SPRMXH.pyth-storage-v3')],
network: getConfig().VITE_NETWORK,
postConditions: [],
postConditionMode: 'deny'
});
txId = response.txid;
Note the subtle change in the way the contract id and the post condition mode is passed.
Conclusions
With version 8 of @stacks/connect
, developers gain a more unified, wallet-agnostic API that drastically simplifies integration with Stacks-compatible wallets. By adopting the new request()
-based flow, apps become more future-proof and easier to maintain, while also improving the user experience across different wallet providers. If you're still using showConnect
and doContractCall
, now's the time to upgrade. For full guidance, check the official docs and start modernising your Stacks app today.
Subscribe to my newsletter
Read articles from mike cohen directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
