Overview
The Contract Service provides functions to interact with the Staxiq smart contract on the Stacks blockchain. It handles user profiles, risk preferences, and strategy anchoring with automatic network detection.
Source: src/services/contractService.js
Contract Address: ST9ZZEP9M6VZ9YJA0P69H313CRPV0HQ1ZNPVS8NZ
Contract Name: staxiq-user-profile
Functions
saveRiskProfile
Save a user’s risk profile preference on-chain.
async function saveRiskProfile ( riskLevel : string ) : Promise < string | null >
Risk profile level: "Conservative", "Balanced", or "Aggressive"
Transaction ID if successful, null if failed (fails gracefully without throwing)
Example
Basic Usage
React Component
import { saveRiskProfile } from './services/contractService' ;
// Save user's risk preference
const txid = await saveRiskProfile ( 'Balanced' );
if ( txid ) {
console . log ( 'Risk profile saved:' , txid );
console . log ( 'View on explorer:' , `https://explorer.hiro.so/txid/ ${ txid } ` );
} else {
console . log ( 'Failed to save risk profile' );
}
Implementation Details
Risk levels are mapped to integers for on-chain storage:
const riskMap = {
Conservative: 1 ,
Balanced: 2 ,
Aggressive: 3
};
getUserProfile
Fetch a user’s complete on-chain profile.
async function getUserProfile ( address : string ) : Promise < object | null >
Stacks wallet address (mainnet or testnet)
User profile data from blockchain, or null if not found Risk level (1=Conservative, 2=Balanced, 3=Aggressive)
Total strategies anchored by this user
Block height of last profile update
Example
import { getUserProfile } from './services/contractService' ;
const profile = await getUserProfile ( 'SP2H8PY27SEZ03MWRKS5XABZYQN17ETGQS3527SA5' );
if ( profile ) {
console . log ( 'Risk Profile:' , profile . riskProfile );
console . log ( 'Strategies:' , profile . strategyCount );
} else {
console . log ( 'User has no profile yet' );
}
checkHasProfile
Check if a user has created an on-chain profile.
async function checkHasProfile ( address : string ) : Promise < boolean >
Stacks wallet address to check
true if user has a profile, false otherwise
Example
import { checkHasProfile } from './services/contractService' ;
const hasProfile = await checkHasProfile ( address );
if ( ! hasProfile ) {
// Show onboarding flow
console . log ( 'New user - show onboarding' );
} else {
// Load existing profile
console . log ( 'Returning user' );
}
anchorStrategy
Save a generated strategy to the blockchain for permanent record.
async function anchorStrategy (
strategyHash : string ,
protocol : string
) : Promise < string | null >
Hash or identifier of the strategy (max 64 characters). Typically a SHA-256 hash of the strategy content.
Primary protocol name for this strategy (max 32 characters)
Transaction ID if successful, null if failed
Example
Basic Usage
React Component
import { anchorStrategy } from './services/contractService' ;
// Hash the strategy content
const strategyContent = "Invest 60% in Zest Protocol..." ;
const hash = await crypto . subtle . digest (
'SHA-256' ,
new TextEncoder (). encode ( strategyContent )
);
const strategyHash = Array . from ( new Uint8Array ( hash ))
. map ( b => b . toString ( 16 ). padStart ( 2 , '0' ))
. join ( '' );
// Anchor to blockchain
const txid = await anchorStrategy ( strategyHash , 'Zest Protocol' );
if ( txid ) {
console . log ( 'Strategy anchored on-chain:' , txid );
}
Strategy hashes are truncated to 64 characters and protocol names to 32 characters to fit on-chain constraints.
getStrategyCount
Get the total number of strategies anchored by a user.
async function getStrategyCount ( address : string ) : Promise < number >
User’s Stacks wallet address
Number of strategies saved on-chain (returns 0 if none or error)
Example
import { getStrategyCount } from './services/contractService' ;
const count = await getStrategyCount ( address );
if ( count === 0 ) {
console . log ( 'No strategies yet - new user' );
} else if ( count > 10 ) {
console . log ( 'Power user with' , count , 'strategies' );
}
Network Detection
All functions automatically detect the appropriate network:
function getNetwork () {
return window . location . hostname === 'localhost' ||
window . location . hostname === '127.0.0.1'
? STACKS_TESTNET
: STACKS_MAINNET ;
}
Transaction Configuration
All write operations use these defaults:
Set to AnchorMode.Any - transaction can be included in microblock or anchor block
Set to PostConditionMode.Allow - allows transactions without explicit post-conditions
const txOptions = {
contractAddress: CONTRACT_ADDRESS ,
contractName: CONTRACT_NAME ,
functionName: 'set-risk-profile' ,
functionArgs: [ uintCV ( level )],
network: getNetwork (),
anchorMode: AnchorMode . Any ,
postConditionMode: PostConditionMode . Allow ,
};
Error Handling
All functions handle errors gracefully:
Write functions (saveRiskProfile, anchorStrategy) return null on error
Read functions (getUserProfile, checkHasProfile) return safe defaults
Count functions (getStrategyCount) return 0 on error
Errors are logged to console with warnings
try {
const result = await fetchCallReadOnlyFunction ({ ... });
return cvToJSON ( result );
} catch ( err ) {
console . warn ( 'Get profile failed:' , err );
return null ;
}
No exceptions are thrown - all errors result in safe fallback values.
Complete Example
import {
saveRiskProfile ,
getUserProfile ,
checkHasProfile ,
anchorStrategy ,
getStrategyCount ,
} from './services/contractService' ;
async function handleNewUser ( address ) {
// Check if user exists
const hasProfile = await checkHasProfile ( address );
if ( ! hasProfile ) {
// Create new profile
console . log ( 'New user - creating profile' );
const txid = await saveRiskProfile ( 'Balanced' );
console . log ( 'Profile created:' , txid );
} else {
// Load existing profile
const profile = await getUserProfile ( address );
const strategyCount = await getStrategyCount ( address );
console . log ( 'Returning user' );
console . log ( 'Risk level:' , profile . riskProfile );
console . log ( 'Total strategies:' , strategyCount );
}
}
async function handleStrategyCreation ( strategyText , primaryProtocol ) {
// Generate hash
const encoder = new TextEncoder ();
const data = encoder . encode ( strategyText );
const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , data );
const hashArray = Array . from ( new Uint8Array ( hashBuffer ));
const hashHex = hashArray . map ( b => b . toString ( 16 ). padStart ( 2 , '0' )). join ( '' );
// Save to blockchain
const txid = await anchorStrategy ( hashHex , primaryProtocol );
if ( txid ) {
console . log ( 'Strategy anchored successfully' );
return {
success: true ,
txid ,
explorerUrl: `https://explorer.hiro.so/txid/ ${ txid } ` ,
};
}
return { success: false };
}
Dependencies
Required packages:
{
"@stacks/transactions" : "^6.x" ,
"@stacks/blockchain-api-client" : "^7.x" ,
"@stacks/network" : "^6.x"
}
Imports:
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' ;
Best Practices
Check Before Write Use checkHasProfile before creating profiles to avoid duplicate transactions
Hash Strategies Always hash strategy content before anchoring to ensure data integrity
Handle Nulls Check for null returns and provide fallback UI for failed transactions
Show Transaction Links Display explorer links so users can verify their on-chain data