@mustafair/reputation-sdk
Version:
Official SDK for integrating CARV ID authentication and MustaFair reputation systems into decentralized applications
368 lines (364 loc) • 13.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
class Fair3ReputationSDK {
constructor(baseUrl = 'https://musta-fair.vercel.app', timeout = 10000, authConfig) {
this.walletConnection = null;
this.authConfig = null;
this.baseUrl = baseUrl.replace(/\/$/, '');
this.timeout = timeout;
this.authConfig = {
contractAddress: authConfig?.contractAddress || Fair3ReputationSDK.DEFAULT_CONTRACTS[97],
chainId: authConfig?.chainId || 97,
requiredChains: authConfig?.requiredChains || [97, 31337],
signMessageTemplate: authConfig?.signMessageTemplate || 'Sign this message to authenticate with CARV ID: {timestamp}',
...authConfig
};
}
async initialize(walletConnection) {
if (walletConnection) {
this.walletConnection = walletConnection;
}
if (this.walletConnection && !this.isValidAddress(this.walletConnection.address)) {
throw new Error('Invalid wallet address in connection');
}
}
setWalletConnection(connection) {
this.walletConnection = connection;
}
getWalletConnection() {
return this.walletConnection;
}
isWalletReady() {
return !!(this.walletConnection?.isConnected &&
this.walletConnection?.address &&
this.authConfig?.requiredChains?.includes(this.walletConnection.chainId));
}
async authenticateWithCarvId(signMessage, options) {
if (!this.walletConnection?.isConnected) {
return { success: false, error: 'Wallet not connected' };
}
if (!this.isWalletReady()) {
return { success: false, error: 'Wallet not on supported network' };
}
try {
const hasIdentity = await this.hasCarvId(this.walletConnection.address);
if (!hasIdentity) {
return { success: false, error: 'No CARV ID found for this wallet. Please mint a CARV ID first.' };
}
const timestamp = Date.now();
const message = this.authConfig.signMessageTemplate.replace('{timestamp}', timestamp.toString());
const signature = await signMessage(message);
const profile = await this.getCarvIdProfile(this.walletConnection.address);
const authData = {
address: this.walletConnection.address,
tokenId: profile.tokenId || '',
signature,
message,
metadata: options?.fetchMetadata ? profile.metadata : undefined,
web2Achievements: options?.fetchWeb2Achievements ? profile.web2Achievements : undefined,
identityHash: this.generateIdentityHash({
address: this.walletConnection.address,
tokenId: profile.tokenId || '',
timestamp
})
};
return {
success: true,
user: authData
};
}
catch (error) {
return {
success: false,
error: error.message || 'Authentication failed'
};
}
}
generateIdentityHash(data) {
const str = JSON.stringify(data);
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16);
}
async prepareCarvIdMint(recipient) {
if (!this.authConfig) {
throw new Error('Authentication config not set');
}
const targetRecipient = recipient || this.walletConnection?.address;
if (!targetRecipient) {
throw new Error('No recipient address provided');
}
return {
to: this.authConfig.contractAddress,
data: `0x40c10f19${targetRecipient.slice(2).padStart(64, '0')}${'1'.padStart(64, '0')}`,
value: '0',
chainId: this.authConfig.chainId
};
}
static getSupportedChains() {
return Fair3ReputationSDK.DEFAULT_CHAINS;
}
static getDefaultContracts() {
return Fair3ReputationSDK.DEFAULT_CONTRACTS;
}
isValidAddress(address) {
return /^0x[a-fA-F0-9]{40}$/.test(address);
}
async makeRequest(url) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(url, {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
clearTimeout(timeoutId);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `HTTP ${response.status}: ${response.statusText}`);
}
return data;
}
catch (error) {
if (error.name === 'AbortError') {
throw new Error(`Request timeout after ${this.timeout}ms`);
}
throw error;
}
}
async getProfile(address) {
if (!this.isValidAddress(address)) {
throw new Error('Invalid Ethereum address format');
}
const response = await this.makeRequest(`${this.baseUrl}/api/public/reputation/${address}`);
if (!response.success) {
throw new Error(response.error || 'Failed to fetch profile data');
}
return response.data;
}
async getCarvIdProfile(address) {
if (!this.isValidAddress(address)) {
throw new Error('Invalid Ethereum address format');
}
const response = await this.makeRequest(`${this.baseUrl}/api/public/carv-id/${address}`);
if (!response.success) {
throw new Error(response.error || 'Failed to fetch CARV ID profile');
}
return response.data;
}
async getReputation(address) {
const profile = await this.getProfile(address);
return profile.reputation;
}
async getLeaderboard(filters = {}) {
const params = new URLSearchParams();
if (filters.page)
params.append('page', filters.page.toString());
if (filters.limit)
params.append('limit', filters.limit.toString());
if (filters.tier)
params.append('tier', filters.tier);
if (filters.sortBy)
params.append('sortBy', filters.sortBy);
if (filters.sortOrder)
params.append('sortOrder', filters.sortOrder);
const url = `${this.baseUrl}/api/public/leaderboard${params.toString() ? '?' + params.toString() : ''}`;
const response = await this.makeRequest(url);
if (!response.success) {
throw new Error(response.error || 'Failed to fetch leaderboard');
}
return response.data;
}
async getStats() {
const response = await this.makeRequest(`${this.baseUrl}/api/public/stats`);
if (!response.success) {
throw new Error(response.error || 'Failed to fetch platform statistics');
}
return response.data;
}
async getTopPerformers(tier, limit = 10) {
const leaderboard = await this.getLeaderboard({
tier,
limit,
sortBy: 'score',
sortOrder: 'desc'
});
return leaderboard.reputations;
}
async hasReputation(address) {
try {
const reputation = await this.getReputation(address);
return reputation !== null && reputation.isActive;
}
catch (error) {
return false;
}
}
async hasCarvId(address) {
try {
const profile = await this.getCarvIdProfile(address);
return profile.hasIdentity;
}
catch (error) {
return false;
}
}
async getTierInfo(address) {
const reputation = await this.getReputation(address);
if (!reputation) {
return null;
}
return {
tier: reputation.tier,
score: reputation.contributionScore,
tierLevel: reputation.tierLevel
};
}
async searchByTier(tier, page = 1, limit = 20) {
const leaderboard = await this.getLeaderboard({ tier, page, limit });
return leaderboard.reputations;
}
async getDocs() {
const response = await this.makeRequest(`${this.baseUrl}/api/public/docs`);
return response;
}
static create(baseUrl, timeout, authConfig) {
return new Fair3ReputationSDK(baseUrl, timeout, authConfig);
}
static validateAddresses(addresses) {
const valid = [];
const invalid = [];
addresses.forEach(addr => {
if (/^0x[a-fA-F0-9]{40}$/.test(addr)) {
valid.push(addr);
}
else {
invalid.push(addr);
}
});
return { valid, invalid };
}
static getTierValue(tier) {
const tierValues = { 'Bronze': 0, 'Silver': 1, 'Gold': 2, 'Platinum': 3 };
return tierValues[tier] || 0;
}
static formatScore(score) {
if (score >= 1000000)
return `${(score / 1000000).toFixed(1)}M`;
if (score >= 1000)
return `${(score / 1000).toFixed(1)}K`;
return score.toString();
}
async checkReputationRequirements(address, requirements) {
const reasons = [];
try {
const profile = await this.getProfile(address);
if (requirements.requireCarvId && !profile.carvId) {
reasons.push('CARV ID required but not found');
}
if (!profile.reputation) {
reasons.push('No reputation NFT found');
return { meets: false, reasons };
}
if (requirements.minScore && profile.reputation.contributionScore < requirements.minScore) {
reasons.push(`Score ${profile.reputation.contributionScore} below minimum ${requirements.minScore}`);
}
if (requirements.minTier) {
const currentTierValue = Fair3ReputationSDK.getTierValue(profile.reputation.tier);
const requiredTierValue = Fair3ReputationSDK.getTierValue(requirements.minTier);
if (currentTierValue < requiredTierValue) {
reasons.push(`Tier ${profile.reputation.tier} below minimum ${requirements.minTier}`);
}
}
return { meets: reasons.length === 0, reasons };
}
catch (error) {
reasons.push(`Error checking requirements: ${error}`);
return { meets: false, reasons };
}
}
async getBatchProfiles(addresses, options) {
const maxConcurrent = options?.maxConcurrent || 5;
const results = [];
for (let i = 0; i < addresses.length; i += maxConcurrent) {
const batch = addresses.slice(i, i + maxConcurrent);
const batchPromises = batch.map(async (address) => {
try {
const profile = await this.getProfile(address);
return { address, profile };
}
catch (error) {
if (options?.includeErrors) {
return { address, error: error.message };
}
return { address };
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
}
Fair3ReputationSDK.DEFAULT_CONTRACTS = {
97: "0x742B6A2A5e29Ad0C20a78B5b6dE55fB2E8B1e8C3",
31337: "0x742B6A2A5e29Ad0C20a78B5b6dE55fB2E8B1e8C3",
};
Fair3ReputationSDK.DEFAULT_CHAINS = [
{
id: 97,
name: 'BSC Testnet',
rpcUrls: ['https://data-seed-prebsc-1-s1.binance.org:8545/'],
nativeCurrency: { name: 'BNB', symbol: 'tBNB', decimals: 18 }
},
{
id: 31337,
name: 'Localhost',
rpcUrls: ['http://127.0.0.1:8545'],
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 }
}
];
const createSDKHooks = (sdk) => {
return {
useProfile: (address) => {
return {
data: null,
loading: false,
error: null,
refetch: async () => address ? await sdk.getProfile(address) : null
};
},
useAuthentication: () => {
return {
authenticate: sdk.authenticateWithCarvId.bind(sdk),
isReady: sdk.isWalletReady(),
connection: sdk.getWalletConnection()
};
}
};
};
const FAIR3_CONSTANTS = {
SUPPORTED_CHAINS: Fair3ReputationSDK.DEFAULT_CHAINS,
CONTRACT_ADDRESSES: Fair3ReputationSDK.DEFAULT_CONTRACTS,
TIER_VALUES: { 'Bronze': 0, 'Silver': 1, 'Gold': 2, 'Platinum': 3 },
API_ENDPOINTS: {
REPUTATION: '/api/public/reputation',
CARV_ID: '/api/public/carv-id',
LEADERBOARD: '/api/public/leaderboard',
STATS: '/api/public/stats'
}
};
const fair3SDK = new Fair3ReputationSDK();
exports.FAIR3_CONSTANTS = FAIR3_CONSTANTS;
exports.Fair3ReputationSDK = Fair3ReputationSDK;
exports.createSDKHooks = createSDKHooks;
exports.default = Fair3ReputationSDK;
exports.fair3SDK = fair3SDK;
//# sourceMappingURL=index.js.map