Skip to main content

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>
riskLevel
string
required
Risk profile level: "Conservative", "Balanced", or "Aggressive"
txid
string | null
Transaction ID if successful, null if failed (fails gracefully without throwing)

Example

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>
address
string
required
Stacks wallet address (mainnet or testnet)
profile
object | null
User profile data from blockchain, or null if not found
riskProfile
number
Risk level (1=Conservative, 2=Balanced, 3=Aggressive)
strategyCount
number
Total strategies anchored by this user
lastUpdated
number
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>
address
string
required
Stacks wallet address to check
hasProfile
boolean
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>
strategyHash
string
required
Hash or identifier of the strategy (max 64 characters). Typically a SHA-256 hash of the strategy content.
protocol
string
required
Primary protocol name for this strategy (max 32 characters)
txid
string | null
Transaction ID if successful, null if failed

Example

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>
address
string
required
User’s Stacks wallet address
count
number
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;
}
Used when:
  • Running on localhost
  • Running on 127.0.0.1
  • Development environment
Network: Stacks Testnet
Explorer: https://explorer.hiro.so?chain=testnet

Transaction Configuration

All write operations use these defaults:
anchorMode
AnchorMode
Set to AnchorMode.Any - transaction can be included in microblock or anchor block
postConditionMode
PostConditionMode
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

Build docs developers (and LLMs) love