Overview
This guide shows you how to integrate the Staxiq User Profile contract into your web application using the Stacks JavaScript SDKs.
Prerequisites
Install Dependencies
Install the required Stacks packages:npm install @stacks/transactions @stacks/blockchain-api-client @stacks/network @stacks/connect
Wallet Integration
Users need a Stacks wallet (Hiro, Xverse, or Leather) to interact with smart contracts.
Network Configuration
Decide whether to use testnet or mainnet.
Contract Service Implementation
Staxiq provides a contractService.js module that wraps all contract interactions:
src/services/contractService.js
import {
makeContractCall,
stringAsciiCV,
uintCV,
AnchorMode,
PostConditionMode,
broadcastTransaction,
} from '@stacks/transactions';
import {
fetchCallReadOnlyFunction,
cvToJSON,
} from '@stacks/blockchain-api-client';
import { STACKS_TESTNET, STACKS_MAINNET } from '@stacks/network';
const CONTRACT_ADDRESS = 'ST9ZZEP9M6VZ9YJA0P69H313CRPV0HQ1ZNPVS8NZ';
const CONTRACT_NAME = 'staxiq-user-profile';
function getNetwork() {
return window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
? STACKS_TESTNET
: STACKS_MAINNET;
}
Writing Data (Public Functions)
Public functions modify blockchain state and require user wallet signatures.
Save Risk Profile
Allow users to set their risk tolerance:
src/services/contractService.js:28
export async function saveRiskProfile(riskLevel) {
try {
const riskMap = { Conservative: 1, Balanced: 2, Aggressive: 3 };
const level = riskMap[riskLevel] || 2;
const network = getNetwork();
const txOptions = {
contractAddress: CONTRACT_ADDRESS,
contractName: CONTRACT_NAME,
functionName: 'set-risk-profile',
functionArgs: [uintCV(level)],
network,
anchorMode: AnchorMode.Any,
postConditionMode: PostConditionMode.Allow,
};
const transaction = await makeContractCall(txOptions);
const result = await broadcastTransaction({ transaction, network });
console.log('✅ Risk profile saved:', result.txid);
return result.txid;
} catch (err) {
console.warn('Risk profile save failed:', err);
return null;
}
}
One of: "Conservative", "Balanced", or "Aggressive"
Transaction ID on success, null on failure
Usage Example
import { saveRiskProfile } from './services/contractService';
import { useState } from 'react';
function RiskProfileForm() {
const [selectedRisk, setSelectedRisk] = useState('Balanced');
const [txId, setTxId] = useState(null);
const handleSubmit = async () => {
const result = await saveRiskProfile(selectedRisk);
if (result) {
setTxId(result);
alert(`Profile saved! TX: ${result}`);
} else {
alert('Failed to save profile');
}
};
return (
<div>
<select value={selectedRisk} onChange={(e) => setSelectedRisk(e.target.value)}>
<option value="Conservative">Conservative</option>
<option value="Balanced">Balanced</option>
<option value="Aggressive">Aggressive</option>
</select>
<button onClick={handleSubmit}>Save Risk Profile</button>
</div>
);
}
Anchor Strategy
Save an AI-generated strategy hash on-chain:
src/services/contractService.js:90
export async function anchorStrategy(strategyHash, protocol) {
try {
const network = getNetwork();
const txOptions = {
contractAddress: CONTRACT_ADDRESS,
contractName: CONTRACT_NAME,
functionName: 'save-strategy',
functionArgs: [
stringAsciiCV(strategyHash.slice(0, 64)),
stringAsciiCV(protocol.slice(0, 32)),
],
network,
anchorMode: AnchorMode.Any,
postConditionMode: PostConditionMode.Allow,
};
const transaction = await makeContractCall(txOptions);
const result = await broadcastTransaction({ transaction, network });
console.log('✅ Strategy anchored:', result.txid);
return result.txid;
} catch (err) {
console.warn('Strategy anchoring failed:', err);
return null;
}
}
64-character hex string (SHA-256 hash of strategy)
Protocol name (max 32 characters), e.g., "ALEX", "Velar"
Transaction ID on success, null on failure
Usage Example
import { anchorStrategy } from './services/contractService';
import crypto from 'crypto';
function StrategyAnchor({ strategy }) {
const handleAnchor = async () => {
// Hash the strategy
const hash = crypto
.createHash('sha256')
.update(JSON.stringify(strategy))
.digest('hex');
// Anchor to blockchain
const txId = await anchorStrategy(hash, strategy.protocol);
if (txId) {
console.log('Strategy anchored with TX:', txId);
// Store txId in your database alongside the strategy
}
};
return (
<button onClick={handleAnchor}>
Anchor Strategy to Blockchain
</button>
);
}
Reading Data (Read-Only Functions)
Read-only functions are free to call and don’t require wallet signatures.
Get User Profile
Retrieve a user’s complete profile:
src/services/contractService.js:55
export async function getUserProfile(address) {
try {
const result = await fetchCallReadOnlyFunction({
contractAddress: CONTRACT_ADDRESS,
contractName: CONTRACT_NAME,
functionName: 'get-user-profile',
functionArgs: [],
senderAddress: address,
network: getNetwork(),
});
return cvToJSON(result);
} catch (err) {
console.warn('Get profile failed:', err);
return null;
}
}
Stacks wallet address (principal)
Profile object or null if not found
1 = Conservative, 2 = Balanced, 3 = Aggressive
Block height when created
Block height of last update
Usage Example
import { getUserProfile } from './services/contractService';
import { useEffect, useState } from 'react';
function ProfileDisplay({ userAddress }) {
const [profile, setProfile] = useState(null);
useEffect(() => {
async function loadProfile() {
const data = await getUserProfile(userAddress);
setProfile(data);
}
loadProfile();
}, [userAddress]);
if (!profile) return <p>No profile found</p>;
const riskLabels = { 1: 'Conservative', 2: 'Balanced', 3: 'Aggressive' };
return (
<div>
<h3>User Profile</h3>
<p>Risk Level: {riskLabels[profile.risk_level]}</p>
<p>Strategies Saved: {profile.strategy_count}</p>
<p>Member Since Block: {profile.created_at}</p>
</div>
);
}
Check if User Has Profile
src/services/contractService.js:73
export async function checkHasProfile(address) {
try {
const result = await fetchCallReadOnlyFunction({
contractAddress: CONTRACT_ADDRESS,
contractName: CONTRACT_NAME,
functionName: 'has-profile',
functionArgs: [],
senderAddress: address,
network: getNetwork(),
});
return cvToJSON(result).value;
} catch (err) {
return false;
}
}
true if user has a profile, false otherwise
Usage Example
import { checkHasProfile } from './services/contractService';
import { useEffect, useState } from 'react';
function OnboardingCheck({ userAddress, children }) {
const [hasProfile, setHasProfile] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function check() {
const result = await checkHasProfile(userAddress);
setHasProfile(result);
setLoading(false);
}
check();
}, [userAddress]);
if (loading) return <p>Checking profile...</p>;
if (!hasProfile) return <RiskProfileSetup />;
return children; // User has profile, show main app
}
Get Strategy Count
src/services/contractService.js:118
export async function getStrategyCount(address) {
try {
const result = await fetchCallReadOnlyFunction({
contractAddress: CONTRACT_ADDRESS,
contractName: CONTRACT_NAME,
functionName: 'get-strategy-count',
functionArgs: [],
senderAddress: address,
network: getNetwork(),
});
return cvToJSON(result).value || 0;
} catch (err) {
return 0;
}
}
Total number of strategies (0 if none)
Wallet Connection
Before users can interact with contracts, they need to connect their wallet using Stacks Connect:
import { showConnect, AppConfig, UserSession } from '@stacks/connect';
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
export function connectWallet() {
showConnect({
appDetails: {
name: 'Staxiq',
icon: window.location.origin + '/logo.png',
},
redirectTo: '/',
onFinish: () => {
window.location.reload();
},
userSession,
});
}
export function getUserAddress() {
if (userSession.isUserSignedIn()) {
const userData = userSession.loadUserData();
return userData.profile.stxAddress.testnet; // or .mainnet
}
return null;
}
export function disconnectWallet() {
userSession.signUserOut();
window.location.reload();
}
Usage in React
import { connectWallet, getUserAddress, disconnectWallet } from './services/walletService';
import { useEffect, useState } from 'react';
function WalletButton() {
const [address, setAddress] = useState(null);
useEffect(() => {
const addr = getUserAddress();
setAddress(addr);
}, []);
if (address) {
return (
<div>
<p>{address.slice(0, 8)}...{address.slice(-4)}</p>
<button onClick={disconnectWallet}>Disconnect</button>
</div>
);
}
return <button onClick={connectWallet}>Connect Wallet</button>;
}
Complete Integration Example
Here’s a complete example showing the full user flow:
import {
saveRiskProfile,
getUserProfile,
checkHasProfile,
anchorStrategy,
getStrategyCount
} from './services/contractService';
import { getUserAddress } from './services/walletService';
import crypto from 'crypto';
async function completeUserFlow() {
// 1. Get user's wallet address
const userAddress = getUserAddress();
if (!userAddress) {
console.log('Please connect wallet first');
return;
}
// 2. Check if user has a profile
const hasProfile = await checkHasProfile(userAddress);
if (!hasProfile) {
// 3. Create profile if needed
console.log('Creating risk profile...');
const txId = await saveRiskProfile('Balanced');
console.log('Profile created:', txId);
// Wait for transaction to confirm (~10 minutes)
await waitForConfirmation(txId);
}
// 4. Get user profile
const profile = await getUserProfile(userAddress);
console.log('Profile:', profile);
// 5. Generate and anchor strategy
const strategy = {
protocol: 'ALEX',
actions: ['stake', 'farm'],
expectedAPY: 12.5,
riskLevel: profile.risk_level
};
const hash = crypto
.createHash('sha256')
.update(JSON.stringify(strategy))
.digest('hex');
const strategyTxId = await anchorStrategy(hash, 'ALEX');
console.log('Strategy anchored:', strategyTxId);
// 6. Get total strategy count
const count = await getStrategyCount(userAddress);
console.log(`User has ${count} strategies`);
}
function waitForConfirmation(txId) {
return new Promise((resolve) => {
// Poll for transaction confirmation
const interval = setInterval(async () => {
const tx = await fetch(`https://api.testnet.hiro.so/extended/v1/tx/${txId}`);
const data = await tx.json();
if (data.tx_status === 'success') {
clearInterval(interval);
resolve();
}
}, 10000); // Check every 10 seconds
});
}
Error Handling
Handle common errors gracefully:
import { saveRiskProfile } from './services/contractService';
async function saveProfileWithErrorHandling(riskLevel) {
try {
const txId = await saveRiskProfile(riskLevel);
if (!txId) {
throw new Error('Transaction failed to broadcast');
}
return { success: true, txId };
} catch (error) {
if (error.message.includes('user canceled')) {
return { success: false, error: 'User canceled transaction' };
}
if (error.message.includes('insufficient funds')) {
return { success: false, error: 'Insufficient STX for gas fees' };
}
return { success: false, error: 'Unknown error occurred' };
}
}
Testing Integration
Always test on testnet before deploying to mainnet:
- Use testnet STX from the Stacks Faucet
- Connect with a testnet wallet
- Verify transactions on Testnet Explorer
Next Steps
Function Reference
See detailed function documentation
Testing
Learn how to test contracts locally
Deployment
Deploy contracts to testnet or mainnet
Stacks.js Docs
Official Stacks JavaScript documentation