feat: Add typed decoder utilities for contract return values#288
Merged
manoahLinks merged 2 commits intoApr 27, 2026
Merged
Conversation
- Implement comprehensive decoder library with 30+ decoders - Add primitive decoders (string, number, bigint, boolean, bytes, address) - Add composite decoders (array, tuple, struct, map, option) - Add Soroban-specific decoders (u32, u64, u128, i32, i64, i128, BytesN) - Add utility decoders (transform, validate, default, oneOf, enum, literal) - Add pre-built contract decoders (Event, TicketTier, BuyerPurchase, TBA Token) - Include safe decoding with result types - Add comprehensive test suite with 50+ test cases - Include detailed documentation and 15 usage examples Resolves crowdpass-live#221
|
@rahimatonize Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Typed Decoder Utilities for Contract Return Values
🎯 Overview
Implements a comprehensive typed decoder library for safely parsing and validating Soroban contract return values in TypeScript. This enhancement eliminates unsafe type assertions and provides explicit validation with clear error messages.
🚀 What's New
Core Features
30+ Decoder Functions: Complete library for type-safe parsing
Composable Architecture: Build complex decoders from simple primitives
Type Safety: Full TypeScript support with type inference
Clear Error Messages: Detailed error context for debugging
Safe Decoding: Optional result-based decoding without exceptions
Pre-built Contract Decoders: Ready-to-use decoders for common contract types
Decoder Categories
Primitive Decoders
decodeString, decodeNumber, decodeBigInt, decodeBoolean
decodeBytes, decodeAddress, decodeSymbol
Composite Decoders
decodeArray / decodeVec - Arrays with element validation
decodeTuple - Fixed-length tuples with mixed types
decodeStruct - Objects with field validation
decodeMap - Key-value maps
decodeOption - Optional values (handles Soroban Option)
Soroban-Specific Decoders
Unsigned integers: decodeU32, decodeU64, decodeU128
Signed integers: decodeI32, decodeI64, decodeI128
Fixed bytes: decodeBytesN(size)
Unit type: decodeVoid
Utility Decoders
decodeTransform - Decode and transform values
decodeValidate - Decode with custom validation
decodeWithDefault - Decode with fallback values
decodeOneOf - Try multiple decoders
decodeEnum - Validate enum values
decodeLiteral - Match exact values
Contract Decoders
ContractDecoder.event() - Event struct
ContractDecoder.ticketTier() - TicketTier struct
ContractDecoder.buyerPurchase() - BuyerPurchase struct
ContractDecoder.tbaToken() - TBA token tuple
📝 Changes
New Files
decoders.ts
(650 lines)
Complete decoder library implementation
DecoderError class for structured errors
Safe decoding utilities
Contract-specific decoders
decoders.test.ts
(450 lines)
Comprehensive test suite with 50+ test cases
Tests for all decoder types
Error handling validation
Edge case coverage
DECODERS.md (800 lines)
Complete documentation
Usage examples for all decoders
Best practices guide
API reference
Troubleshooting guide
decoder-usage.ts
(500 lines)
15 practical examples
Real-world usage patterns
Integration with SDK
Modified Files
index.ts
: Added decoder exports to public API
README.md: Added decoder utilities documentation
💻 Usage
Basic Usage
import { decodeString, decodeNumber, decodeArray } from "@crowdpass/tokenbound-sdk";
// Decode primitives
const name = decodeString("Alice"); // "Alice"
const age = decodeNumber(25); // 25
// Decode arrays
const ids = decodeArray(decodeNumber)([1, 2, 3]);
// [1, 2, 3]
Soroban Types
import { decodeU32, decodeU128, decodeI128, decodeAddress } from "@crowdpass/tokenbound-sdk";
// Unsigned integers
const eventId = decodeU32(123);
const totalTickets = decodeU128(1000n);
// Signed integers
const price = decodeI128(5000000000n);
// Addresses
const organizer = decodeAddress("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
Structs and Complex Types
import { decodeStruct, decodeU32, decodeString, decodeBoolean } from "@crowdpass/tokenbound-sdk";
// Define struct decoder
const decodeUser = decodeStruct({
id: decodeU32,
name: decodeString,
email: decodeString,
active: decodeBoolean,
});
// Use it
const user = decodeUser({
id: 1,
name: "Alice",
email: "alice@example.com",
active: true,
});
Contract Decoders
import { ContractDecoder } from "@crowdpass/tokenbound-sdk";
// Decode event response
const event = ContractDecoder.event()({
id: 1,
theme: "Web3 Conference",
organizer: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
event_type: "Conference",
total_tickets: 500n,
tickets_sold: 250n,
ticket_price: 1000000000n,
start_date: 1234567890,
end_date: 1234567900,
is_canceled: false,
ticket_nft_addr: "CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
payment_token: "CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
});
// Decode array of tiers
const tiers = decodeArray(ContractDecoder.ticketTier())(rawTiers);
Advanced Composition
import {
decodeStruct,
decodeArray,
decodeOption,
decodeU32,
decodeString,
decodeI128,
} from "@crowdpass/tokenbound-sdk";
// Compose complex decoders
const decodeEventWithTiers = decodeStruct({
id: decodeU32,
name: decodeString,
tiers: decodeArray(
decodeStruct({
name: decodeString,
price: decodeI128,
available: decodeOption(decodeU32),
})
),
});
const event = decodeEventWithTiers({
id: 1,
name: "Conference",
tiers: [
{ name: "General", price: 1000000000n, available: 100 },
{ name: "VIP", price: 5000000000n, available: null },
],
});
Transform and Validate
import { decodeTransform, decodeValidate, decodeI128, decodeString } from "@crowdpass/tokenbound-sdk";
// Transform decoded value
const decodeUppercase = decodeTransform(
decodeString,
(s) => s.toUpperCase()
);
const theme = decodeUppercase("web3 conference");
// "WEB3 CONFERENCE"
// Validate decoded value
const decodePositivePrice = decodeValidate(
decodeI128,
(price) => price > 0n,
"Price must be positive"
);
const price = decodePositivePrice(1000000000n);
// 1000000000n (validated)
Safe Decoding
import { safeDecode, decodeNumber } from "@crowdpass/tokenbound-sdk";
// Returns result object instead of throwing
const result = safeDecode(decodeNumber, "invalid");
if (result.success) {
console.log("Value:", result.value);
} else {
console.error("Error:", result.error.message);
}
Integration with SDK
import { createTokenboundSdk, ContractDecoder } from "@crowdpass/tokenbound-sdk";
const sdk = createTokenboundSdk({
// ... config
});
// Get raw response
const rawEvent = await sdk.eventManager.getEvent(1);
// Decode with explicit validation
const event = ContractDecoder.event()(rawEvent);
// Now you have type-safe, validated data
console.log(event.theme); // string
console.log(event.total_tickets); // bigint
console.log(event.is_canceled); // boolean
🎯 Benefits
For Developers
✅ Type Safety - Compile-time and runtime type checking
✅ Clear Errors - Detailed error messages with context
✅ Composable - Build complex decoders from simple ones
✅ Reusable - Define once, use everywhere
✅ Testable - Easy to test with mock data
For Code Quality
✅ No Unsafe Casts - Eliminates as type assertions
✅ Explicit Validation - Clear data validation logic
✅ Early Detection - Catch errors at parse time
✅ Self-Documenting - Decoders document expected structure
For Maintenance
✅ Centralized Logic - Single source of truth for parsing
✅ Easy Updates - Change decoder, update everywhere
✅ Refactor-Friendly - TypeScript catches breaking changes
🧪 Testing
cd soroban-client/sdk
npm test -- decoders.test.ts
Test Coverage:
✅ Primitive decoders (10+ tests)
✅ Composite decoders (15+ tests)
✅ Soroban-specific decoders (10+ tests)
✅ Utility decoders (10+ tests)
✅ Contract decoders (5+ tests)
✅ Error handling (10+ tests)
📚 Documentation
DECODERS.md: Complete documentation (800+ lines)
examples/decoder-usage.ts: 15 practical examples (500+ lines)
README.md: Updated with decoder info
🔄 Migration Guide
From Unsafe Type Assertions
Before:
const event = response as EventRecord;
// No validation, runtime errors possible
// TypeScript can't help if structure changes
After:
const event = ContractDecoder.event()(response);
// ✅ Validated at runtime
// ✅ Type-safe
// ✅ Clear error messages
// ✅ Catches structure mismatches
From Manual Validation
Before:
function parseEvent(raw: any): EventRecord {
if (typeof raw.id !== "number") {
throw new Error("Invalid id");
}
if (typeof raw.theme !== "string") {
throw new Error("Invalid theme");
}
// ... 20+ more checks
return raw as EventRecord;
}
After:
const decodeEvent = ContractDecoder.event();
const event = decodeEvent(raw);
// All validation handled automatically
✅ Checklist
Implementation complete (650+ lines)
Comprehensive tests (50+ test cases)
Documentation written (800+ lines)
Usage examples provided (15 examples)
README updated
Backward compatible (no breaking changes)
TypeScript types complete
Exports added to public API
Error handling implemented
Safe decoding support
📦 Files Changed
7 files changed
2,779 insertions
0 deletions
New Files
decoders.ts
decoders.test.ts
DECODERS.md
decoder-usage.ts
Modified Files
index.ts
README.md
🚦 Ready for Review
This PR is ready for review and testing. All changes are backward compatible and require no modifications to existing code. The decoders are opt-in and can be adopted gradually.
🔮 Future Enhancements
Potential improvements for future PRs:
Schema validation from contract specs
Auto-generate decoders from contract metadata
Performance optimizations for large datasets
Decoder composition helpers
Custom error formatters
closes #221