Skip to content
Open
57 changes: 57 additions & 0 deletions packages/connect-react/src/react/hooks/use-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ const useConnectDispatch = () => {
return dispatch;
};

/**
* React hook that provides wallet connection and transaction capabilities
* for Stacks applications. Must be used within a `ConnectProvider`.
*
* @returns An object with authentication state and action functions for
* wallet connection, contract calls, STX transfers, signing, and more.
*
* @example
* ```tsx
* function MyComponent() {
* const { doAuth, doContractCall, doSTXTransfer } = useConnect();
* return <button onClick={() => doAuth()}>Connect Wallet</button>;
* }
* ```
*/
export const useConnect = () => {
// todo: add custom provider injection in connect context
const { isOpen, isAuthenticating, authData, authOptions, userSession } =
Expand Down Expand Up @@ -93,6 +108,12 @@ export const useConnect = () => {
);
};

/**
* Opens a contract call transaction popup.
*
* @param options - Contract call options including contract address, name, function, and args
* @param provider - Optional specific wallet provider to use
*/
function doContractCall(
options: ContractCallOptions | ContractCallRegularOptions | ContractCallSponsoredOptions,
provider?: StacksProvider
Expand All @@ -107,6 +128,12 @@ export const useConnect = () => {
);
}

/**
* Opens a contract deploy transaction popup.
*
* @param options - Contract deploy options including contract name and code body
* @param provider - Optional specific wallet provider to use
*/
function doContractDeploy(
options: ContractDeployOptions | ContractDeployRegularOptions | ContractDeploySponsoredOptions,
provider?: StacksProvider
Expand All @@ -121,6 +148,12 @@ export const useConnect = () => {
);
}

/**
* Opens a STX transfer transaction popup.
*
* @param options - Transfer options including recipient address, amount, and optional memo
* @param provider - Optional specific wallet provider to use
*/
function doSTXTransfer(
options: STXTransferOptions | STXTransferRegularOptions | STXTransferSponsoredOptions,
provider?: StacksProvider
Expand All @@ -135,6 +168,12 @@ export const useConnect = () => {
);
}

/**
* Opens a profile update request popup.
*
* @param options - Profile update options
* @param provider - Optional specific wallet provider to use
*/
function doProfileUpdate(options: ProfileUpdateRequestOptions, provider?: StacksProvider) {
return openProfileUpdateRequestPopup(
{
Expand All @@ -146,6 +185,12 @@ export const useConnect = () => {
);
}

/**
* Opens a sign transaction popup for signing a pre-built transaction hex.
*
* @param options - Sign transaction options including the transaction hex string
* @param provider - Optional specific wallet provider to use
*/
function doSignTransaction(options: SignTransactionOptions, provider?: StacksProvider) {
return openSignTransaction(
{
Expand All @@ -157,6 +202,12 @@ export const useConnect = () => {
);
}

/**
* Opens a message signing popup for signing an arbitrary string message.
*
* @param options - Signature request options including the message to sign
* @param provider - Optional specific wallet provider to use
*/
function sign(options: SignatureRequestOptions, provider?: StacksProvider) {
return openSignatureRequestPopup(
{
Expand All @@ -182,6 +233,12 @@ export const useConnect = () => {
);
}

/**
* Opens a PSBT (Partially Signed Bitcoin Transaction) signing popup.
*
* @param options - PSBT request options including the base64-encoded PSBT
* @param provider - Optional specific wallet provider to use
*/
function signPsbt(options: PsbtRequestOptions, provider?: StacksProvider) {
return openPsbtRequestPopup(
{
Expand Down
91 changes: 91 additions & 0 deletions packages/connect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,97 @@ try {
}
```

### Common Error Codes

When using `isJsonRpcError` to check errors, you can reference these common error codes:

| Code | Name | Description |
|------|------|-------------|
| `-32700` | `ParseError` | Invalid JSON received by server |
| `-32600` | `InvalidRequest` | Invalid Request object |
| `-32601` | `MethodNotFound` | Method not found/available |
| `-32602` | `InvalidParams` | Invalid method parameters |
| `-32603` | `InternalError` | Internal JSON-RPC error |
| `-32000` | `UserRejection` | User rejected the request |
| `-32001` | `MethodAddressMismatch` | Address mismatch for requested method |
| `-32002` | `MethodAccessDenied` | Access denied for requested method |
| `-32003` | `NetworkError` | Network-related error (e.g., node unavailable) |
| `-32004` | `TimeoutError` | Request timed out |
| `-32005` | `ProviderNotFound` | Wallet provider not found |
| `-31000` | `UnknownError` | Unknown external error |
| `-31001` | `UserCanceled` | User canceled the request |

## Troubleshooting

### Common Issues

#### "No wallet found" or provider not detected

**Symptoms**: `isStacksWalletInstalled()` returns `false`, or connection fails.

**Solutions**:
1. Ensure a Stacks-compatible wallet (Leather, Xverse, etc.) is installed
2. Refresh the page after installing the wallet extension
3. Check if the wallet is enabled for your domain
4. Try using `forceWalletSelect: true` to prompt wallet selection:
```ts
await connect({ forceWalletSelect: true });
```

#### User rejected request (Error code -32000)

**Symptoms**: `JsonRpcError` with code `-32000`.

**Solutions**:
1. This is expected behavior when the user clicks "Reject" or closes the wallet popup
2. Handle this gracefully in your UI:
```ts
try {
await request('stx_transferStx', params);
} catch (error) {
if (isJsonRpcError(error) && error.code === -32000) {
console.log('User rejected - show friendly message');
}
}
```

#### Transaction fails with "Invalid params"

**Symptoms**: `JsonRpcError` with code `-32602`.

**Solutions**:
1. Verify all required parameters are provided
2. Check that amounts are strings (not numbers) for STX transfers
3. Ensure addresses are valid Stacks addresses (starting with `SP` or `ST`)
4. For contract calls, verify function arguments match the contract's expected types

#### Connection works in development but fails in production

**Symptoms**: Wallet connects locally but not on deployed site.

**Solutions**:
1. Ensure your site uses HTTPS (required by most wallets)
2. Check if the wallet has granted permission for your production domain
3. Verify there are no Content Security Policy (CSP) issues blocking the wallet

### Debugging Tips

1. **Enable console logging** to see wallet communication:
```ts
// Check what provider is being used
console.log('Provider:', getStacksProvider());
```

2. **Verify connection state**:
```ts
console.log('Connected:', isConnected());
console.log('Storage:', getLocalStorage());
```

3. **Use `requestRaw` for debugging** to bypass compatibility layers and see raw wallet responses.

4. **Check wallet-specific documentation** - Leather and Xverse have different method support (see Support table below).

## Compatibility

The `request` method by default adds a layer of auto-compatibility for different wallet providers.
Expand Down
59 changes: 59 additions & 0 deletions packages/connect/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { JsonRpcResponseError } from './methods';

/**
* Represents a JSON-RPC 2.0 error with code, message, and optional data.
*
* @see {@link JsonRpcErrorCode} for standard and custom error codes
*
* @example
* ```ts
* const error = new JsonRpcError('User rejected request', JsonRpcErrorCode.UserRejection);
* console.log(error.toString()); // "JsonRpcError (-32000): User rejected request"
* ```
*/
export class JsonRpcError extends Error {
constructor(
public message: string,
Expand All @@ -17,15 +28,47 @@ export class JsonRpcError extends Error {
this.cause = cause;
}

/**
* Creates a JsonRpcError from a JSON-RPC response error object.
*
* @param error - The error object from a JSON-RPC response
* @returns A new JsonRpcError instance
*/
static fromResponse(error: JsonRpcResponseError) {
return new JsonRpcError(error.message, error.code, error.data);
}

/**
* Returns a human-readable string representation of the error.
*
* @returns Formatted error string including name, code, message, and optional data
*/
toString() {
return `${this.name} (${this.code}): ${this.message}${this.data ? `: ${JSON.stringify(this.data)}` : ''}`;
}
}

/**
* Type guard to check if an error is a JsonRpcError.
*
* @param error - The error to check
* @returns `true` if the error is a JsonRpcError instance
*
* @example
* ```ts
* try {
* await request('stx_transferStx', params);
* } catch (error) {
* if (isJsonRpcError(error)) {
* console.log('JSON-RPC Error:', error.code, error.message);
* }
* }
* ```
*/
export function isJsonRpcError(error: unknown): error is JsonRpcError {
return error instanceof JsonRpcError;
}

/**
* Numeric error codes for JSON-RPC errors, used for `.code` in {@link JsonRpcError}.
* Implementation-defined wallet errors range from `-32099` to `-32000`.
Expand Down Expand Up @@ -59,6 +102,21 @@ export enum JsonRpcErrorCode {
/** Access denied for the requested method (implementation-defined wallet error) */
MethodAccessDenied = -32_002,

/** Network-related error, e.g., node unavailable (implementation-defined wallet error) */
NetworkError = -32_003,

/** Request timed out (implementation-defined wallet error) */
TimeoutError = -32_004,

/** Wallet provider not found or not installed (implementation-defined wallet error) */
ProviderNotFound = -32_005,

/** Method is not supported by this wallet (implementation-defined wallet error) */
UnsupportedMethod = -32_006,

/** Invalid or unsupported network configuration (implementation-defined wallet error) */
InvalidNetwork = -32_007,

// CUSTOM ERRORS (Custom range, not inside the JSON-RPC error code range)
/**
* Unknown external error.
Expand All @@ -72,3 +130,4 @@ export enum JsonRpcErrorCode {
*/
UserCanceled = -31_001,
}

17 changes: 14 additions & 3 deletions packages/connect/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,20 @@ export interface ConnectRequestOptions {
* `"WalletConnectProvider"` in the `approvedProviderIds` array.
*/
walletConnect?: Partial<Pick<UniversalConnectorConfig, 'metadata' | 'networks'>> &
Omit<UniversalConnectorConfig, 'metadata' | 'networks'>;
Omit<UniversalConnectorConfig, 'metadata' | 'networks'>;
}

/**
* Sends a raw JSON-RPC request to the given wallet provider.
* Unlike {@link request}, this function does not handle wallet selection,
* UI modals, parameter serialization, or local storage caching.
*
* @param provider - The wallet provider instance to send the request to
* @param method - The JSON-RPC method name (e.g., `'getAddresses'`, `'stx_transferStx'`)
* @param params - Optional parameters for the method
* @returns The raw result from the wallet provider
* @throws {JsonRpcError} If the provider returns an error or fails to respond
*/
export async function requestRaw<M extends keyof MethodsRaw>(
provider: StacksProvider,
method: M,
Expand Down Expand Up @@ -554,9 +565,9 @@ export function serializeParams<M extends keyof Methods>(params: MethodParams<M>
if (typeof value === 'object' && 'type' in value) {
result[key] = POST_CONDITIONS.includes(value.type)
? // Post condition
postConditionToHex(value)
postConditionToHex(value)
: // Clarity value
Cl.serialize(value);
Cl.serialize(value);
}
}

Expand Down
Loading