@seaspark/assets-sdk
v0.1.0
Published
SeaVerse Assets API SDK - Unified multi-tenant asset and credit management service
Readme
@seaspark/assets-sdk
SeaVerse Asset Hub SDK - Unified multi-tenant credit management service (4-pool system)
Overview
Asset Hub SDK provides comprehensive credit management capabilities, supporting a 4-pool credit system (daily/event/monthly/permanent) and traditional credit account queries. All applications (including the SeaVerse platform itself) use the same SDK routing and multi-tenant architecture.
Main API Endpoints:
/sdk/v1/credits/detail- Get 4-pool credit details (recommended)/sdk/v1/credits/account- Get credit account information (deprecated)/sdk/v1/credits/transactions- Query transaction history
Core Concepts
Application ID
Each application has a unique app_id:
- SeaVerse Platform:
appId = "seaverse"(official platform apps like ops-frontend, meta-product) - Third-party Apps:
appId = "game-abc123",appId = "social-xyz789", etc.
Multi-tenant Isolation
- Credit accounts are isolated by
app_id - Each application has its own credit pools and transaction history
- The same user can have different credit accounts across different applications
Installation
npm install @seaspark/assets-sdk
# or
pnpm add @seaspark/assets-sdkFeatures
- 4-Pool Credit System: Supports daily/event/monthly/permanent credit pools
- Expiration Time Tracking: Each credit pool has independent expiration timestamps
- Consumption Priority: Daily → Event → Monthly → Permanent
- Query user credit account information with multi-tenant support
- List transaction history with filtering and pagination
- TypeScript support with complete type definitions
- Read-only API (credit consumption/distribution handled by platform backend)
Quick Start
import { AssetsClient } from '@seaspark/assets-sdk';
// Initialize client for SeaVerse platform
const client = new AssetsClient({
appId: 'seaverse',
token: 'your-bearer-token'
});
// Or initialize for third-party application
const gameClient = new AssetsClient({
appId: 'game-abc123',
token: 'your-bearer-token'
});
// Get credit details (recommended - 4-pool system)
const detail = await client.getCreditDetail();
console.log(`Total Balance: ${detail.total_balance} credits`);
// Display details for each credit pool
detail.pools.forEach(pool => {
const expiryLabel = pool.expires_at === 0
? 'Never expires'
: `Expires in ${Math.floor((pool.expires_at - Date.now()) / 86400000)} days`;
console.log(`${pool.type}: ${pool.balance} (${expiryLabel})`);
});
// List recent transactions
const result = await client.listTransactions({ page_size: 10 });
console.log(`Found ${result.transactions.length} transactions`);
console.log(`Has more: ${result.has_more}`);4-Pool Credit System
Asset Hub SDK uses a 4-pool credit system, each with different expiration rules and purposes:
Credit Pool Types
| Pool Type | Description | Expiration Rules |
|-----------|-------------|------------------|
| daily | Daily credits | Expire at 00:00 UTC next day |
| event | Event credits | Expire at event deadline |
| monthly | Monthly credits | Expire 30 days after last grant |
| permanent | General credits | Never expire |
Consumption Priority
When users consume credits, the system deducts in the following priority:
Daily → Event → Monthly → PermanentThis ensures credits about to expire are consumed first, maximizing user credit value.
Usage Example
const detail = await client.getCreditDetail();
// Calculate total balance
console.log(`Total: ${detail.total_balance} credits`);
// Iterate through credit pools
for (const pool of detail.pools) {
const remaining = pool.expires_at === 0
? Infinity
: pool.expires_at - Date.now();
if (remaining === Infinity) {
console.log(`${pool.type}: ${pool.balance} credits (Never expires)`);
} else {
const days = Math.floor(remaining / 86400000);
const hours = Math.floor((remaining % 86400000) / 3600000);
console.log(`${pool.type}: ${pool.balance} credits (Expires in ${days}d ${hours}h)`);
}
}
// Check for credits expiring soon
const expiringPools = detail.pools.filter(pool => {
if (pool.expires_at === 0) return false;
const daysLeft = (pool.expires_at - Date.now()) / 86400000;
return daysLeft < 3; // Expiring within 3 days
});
if (expiringPools.length > 0) {
console.log('⚠️ Some credits are expiring soon, please use them!');
expiringPools.forEach(pool => {
console.log(` - ${pool.type}: ${pool.balance} credits`);
});
}API Reference
AssetsClient
Constructor
new AssetsClient(options: AssetsClientOptions)Parameters:
appId(string, required): Application ID for multi-tenant isolation'seaverse'for SeaVerse platform'game-abc123'for third-party applications
token(string, required): Bearer token for authenticationbaseURL(string, optional): Base URL for Asset Hub API- Default:
'https://seaspark-wallet-service.dev.seaspark.ai' - Pass empty string
''to use relative URLs (useful with dev proxy)
- Default:
timeout(number, optional): Request timeout in milliseconds, default 30000
Methods
getCreditDetail()
Get credit details (4-pool system) - Recommended
Returns detailed information for 4 credit pools, each with balance and expiration timestamp.
API Endpoint: GET /sdk/v1/credits/detail
Multi-tenant Behavior:
- Credit pools belong to the application specified by
appId - Each application has independent 4-pool system
- Pool data is isolated by
app_id
Request Headers:
Authorization: Bearer <token>- RequiredX-App-ID: <app_id>- Required
async getCreditDetail(): Promise<CreditDetailResponse>Returns:
{
total_balance: string // Total balance across all pools (string for precision)
pools: [ // Array of credit pools (only pools with balance > 0)
{
type: 'daily' | 'event' | 'monthly' | 'permanent'
balance: string // Pool balance (string for precision)
expires_at: number // Expiration timestamp in milliseconds
// 0 = never expires
// > 0 = specific expiration time
}
]
}Credit Pool Types:
daily- Daily credits (expire at 00:00 UTC next day)event- Event credits (expire at event deadline)monthly- Monthly credits (expire 30 days after last grant)permanent- General credits (never expire)
Response Features:
- Only returns credit pools with
balance > 0 expires_atis in milliseconds timestamp (0 means never expires)- Frontend can calculate remaining time based on timestamp
Recommended Refresh Frequency: 30 seconds
Example:
const detail = await client.getCreditDetail();
console.log(`Total Balance: ${detail.total_balance}`);
detail.pools.forEach(pool => {
const label = pool.expires_at === 0
? 'Never expires'
: `Expires in ${Math.floor((pool.expires_at - Date.now()) / 86400000)} days`;
console.log(`${pool.type}: ${pool.balance} (${label})`);
});
// Example output:
// Total Balance: 5240
// daily: 150 (Expires in 0 days)
// event: 500 (Expires in 5 days)
// monthly: 800 (Expires in 28 days)
// permanent: 3790 (Never expires)getCreditAccount()
⚠️ Deprecated: Use
getCreditDetail()for the new 4-pool credit system.
Get authenticated user's credit account information for the specified application.
API Endpoint: GET /sdk/v1/credits/account
Multi-tenant Behavior:
- Credit account belongs to the application specified by
appId - Each application has isolated credit pools
- Account data is isolated by
app_id
Request Headers:
Authorization: Bearer <token>- RequiredX-App-ID: <app_id>- Required
async getCreditAccount(): Promise<CreditAccount>Returns:
{
id: string
user_id: string
app_id: string | null // Application ID (null for platform users)
balance: number // Current available balance
total_earned: number // Total credits earned
total_spent: number // Total credits spent
frozen_balance: number // Frozen balance (unavailable)
last_transaction_id?: string
status: 'active' | 'suspended' | 'frozen'
status_reason?: string // Only present in non-active states
created_at: string // UTC timestamp
updated_at: string // UTC timestamp
last_activity_at?: string // UTC timestamp
}Account Status:
active- Normal, availablesuspended- Suspended (contact support)frozen- Frozen (risk control)
Notes:
- Some fields may be
nullorundefined, handle null values properly - Number fields may be returned as strings, SDK automatically handles type conversion
- Use optional chaining operator
?.to access optional fields
Example:
const account = await client.getCreditAccount();
console.log(`Balance: ${account.balance ?? 0}`);
console.log(`App: ${account.app_id ?? 'N/A'}`);
console.log(`Status: ${account.status}`);
// Safely access optional fields
if (account.last_activity_at) {
console.log(`Last Activity: ${account.last_activity_at}`);
}listTransactions()
List credit transactions for the specified application, with filtering and pagination support.
API Endpoint: GET /sdk/v1/credits/transactions
Multi-tenant Behavior:
- Transactions are filtered by the application specified by
appId - Only returns transactions for the authenticated user in this application
- Transaction history is isolated by
app_id
Request Headers:
Authorization: Bearer <token>- RequiredX-App-ID: <app_id>- Required
async listTransactions(request?: ListTransactionsRequest): Promise<TransactionListResponse>Request Parameters:
type(optional): Filter by transaction type'earn'- Credit earned'spend'- Credit deducted'freeze'- Credit frozen'unfreeze'- Credit unfrozen'refund'- Refund returned'adjust'- Admin adjustment
source(optional): Filter by transaction source (e.g.,'api_call','purchase')status(optional): Filter by status'pending'- Processing'completed'- Completed'failed'- Failed'cancelled'- Cancelled
start_date(optional): Start date filter (RFC3339 format, e.g.,'2024-01-01T00:00:00Z')end_date(optional): End date filter (RFC3339 format, e.g.,'2024-12-31T23:59:59Z')page(optional): Page number (starts at 1, default: 1)page_size(optional): Items per page (min: 1, max: 50, default: 20)
Returns:
{
transactions: CreditTransaction[]
page: number
page_size: number
has_more: boolean
}CreditTransaction:
{
id: string
user_id: string
app_id: string | null // Application ID (null for platform users)
type: 'spend' | 'earn' | 'refund' | 'adjust' | 'freeze' | 'unfreeze'
amount: number // Negative for deduction, positive for addition
balance_before: number
balance_after: number
source: string // Transaction source identifier
source_id?: string // Associated source ID (order/task/request ID)
description: string
status: 'pending' | 'completed' | 'failed' | 'cancelled'
status_reason?: string // Only present in non-completed states
metadata?: Record<string, any> // Custom metadata
created_at: string // UTC timestamp
completed_at?: string // UTC timestamp
}Example:
// Get recent transactions
const result = await client.listTransactions({ page_size: 20 });
// Filter by type
const spending = await client.listTransactions({
type: 'spend',
page: 1,
page_size: 10
});
// Filter by date range
const recent = await client.listTransactions({
start_date: '2024-01-01T00:00:00Z',
end_date: '2024-12-31T23:59:59Z',
status: 'completed'
});
// Pagination
const page2 = await client.listTransactions({
page: 2,
page_size: 20
});
if (page2.has_more) {
// More results available
}setToken()
Update the bearer token for authentication.
Use this method when the token expires or needs to be refreshed.
setToken(token: string): voidExample:
client.setToken('new-bearer-token');setBaseURL()
Update the API base URL.
Use this method to switch between different environments (development/testing/production).
setBaseURL(baseURL: string): voidExample:
client.setBaseURL('https://seaspark-wallet-service.dev.seaspark.ai');setAppId()
Update the application ID for multi-tenant isolation.
Use this method to switch between different applications at runtime.
setAppId(appId: string): voidExample:
// Switch to SeaVerse platform
client.setAppId('seaverse');
// Switch to third-party application
client.setAppId('game-abc123');Error Handling
SDK throws AssetsAPIError for API errors:
import { AssetsAPIError } from '@seaspark/assets-sdk';
try {
const account = await client.getCreditAccount();
} catch (error) {
if (error instanceof AssetsAPIError) {
console.error(`API Error [${error.code}]: ${error.message}`);
console.error('Response:', error.response);
} else {
console.error('Unexpected error:', error);
}
}Common Error Codes:
400- Bad Request (invalid parameters)401- Unauthorized (invalid or expired token)403- Forbidden500- Internal Server Error
Environment Configuration
Different environments have different base URLs:
// Development/Testing environment (default)
const client = new AssetsClient({
appId: 'seaverse',
token: devToken
// baseURL defaults to 'https://seaspark-wallet-service.dev.seaspark.ai'
});
// Production environment
const client = new AssetsClient({
appId: 'seaverse',
baseURL: 'https://seaspark-wallet-service.prod.seaspark.ai', // TODO: 待确认生产环境地址
token: prodToken
});
// Local development
const client = new AssetsClient({
appId: 'seaverse',
baseURL: 'http://localhost:8080',
token: localToken
});Environment Description:
- Development/Testing:
https://seaspark-wallet-service.dev.seaspark.ai(SDK default) - Production: TBD (待确认)
- Local:
http://localhost:8080(local debugging)
Multi-tenant Usage Examples
SeaVerse Platform Application
// Official SeaVerse platform (ops-frontend, meta-product, etc.)
const platformClient = new AssetsClient({
appId: 'seaverse',
token: userToken
});
const account = await platformClient.getCreditAccount();
console.log(`Platform Balance: ${account.balance}`);Third-party Game Application
// Third-party game with its own credit pool
const gameClient = new AssetsClient({
appId: 'game-abc123',
token: userToken
});
const gameAccount = await gameClient.getCreditAccount();
console.log(`Game Balance: ${gameAccount.balance}`);
// Same user, different application - isolated credit accounts
// gameAccount.balance may differ from platformClient balanceSwitching Between Applications
const client = new AssetsClient({
appId: 'seaverse',
token: userToken
});
// Query SeaVerse platform account
const platformAccount = await client.getCreditAccount();
// Switch to game application
client.setAppId('game-abc123');
// Query same user's game account
const gameAccount = await client.getCreditAccount();
// Both accounts belong to same user but are isolatedCredit Units
- 1 credit = 1 unit
- All amounts are represented as integers (no decimals)
Authentication
- Token obtained through SeaVerse auth-service
- Bearer token required in
Authorizationheader - Default token validity: 10 days
- Required scope:
account
Security
Domain Whitelist (Recommended)
For production apps, register allowed domains to prevent unauthorized usage:
Originheader validated against application whitelist- Prevents
app_idtheft and abuse
Rate Limiting
- Rate limiting applied per
app_id - Prevents abuse
Troubleshooting
Common Issues
1. Cannot read properties of undefined error
If you encounter errors like Cannot read properties of undefined (reading 'toLocaleString'), it means some API fields are undefined or null.
Solution:
// ✅ Recommended: Use nullish coalescing operator
const balance = account.balance ?? 0;
const totalEarned = account.total_earned ?? 0;
// ✅ Recommended: Use optional chaining
const lastActivity = account.last_activity_at ?? 'N/A';
// ✅ Recommended: Check value exists before display
if (account.balance !== null && account.balance !== undefined) {
console.log(`Balance: ${account.balance.toLocaleString()}`);
}2. CORS errors
If you encounter CORS errors in the browser, ensure:
- Your domain is added to the application's whitelist
X-App-IDheader is set correctly- Using correct baseURL
3. 401 Unauthorized error
Possible causes:
- Token expired (default validity 10 days)
- Token format incorrect
- Token missing correct scope (needs
accountscope)
Solution:
// Re-obtain token
const newToken = await authService.getToken();
client.setToken(newToken);4. Data type mismatch
API may return numbers as strings, ensure your code can handle this:
// ✅ Safe type conversion
const balance = typeof account.balance === 'string'
? parseFloat(account.balance)
: account.balance;
// ✅ Use SDK-provided type checking
const formatNumber = (value: string | number | null | undefined): number => {
if (value === null || value === undefined) return 0;
const num = typeof value === 'string' ? parseFloat(value) : value;
return isNaN(num) ? 0 : num;
};License
MIT
