Skip to content
Merged
178 changes: 178 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# DashSync iOS

DashSync is a lightweight blockchain client library for iOS/macOS that enables applications to interact with the Dash cryptocurrency network. It supports both Dash Core Network (Layer 1) and Dash Platform (Layer 2).

## Quick Reference

- **Language**: Objective-C with C/C++/Rust interop
- **Build System**: Xcode + CocoaPods
- **Deployment**: iOS 13.0+, macOS 10.15+
- **Pod Name**: `DashSyncPod`

## Build Requirements

```bash
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim

# Install protobuf and grpc
brew install protobuf grpc cmake
```

## Common Commands

```bash
# Run example project
cd Example && pod install && open DashSync.xcworkspace

# Run tests
cd Example && xcodebuild test -workspace DashSync.xcworkspace -scheme DashSync-Example -destination 'platform=iOS Simulator,name=iPhone 15'

# Update pods
cd Example && pod update
```

## Project Structure

```
DashSync/
├── DashSync/shared/ # Main framework source (cross-platform)
│ ├── Models/ # Core domain models (24 subdirectories)
│ ├── Libraries/ # Utility libraries
│ └── DashSync.xcdatamodeld/ # Core Data model (83 entities)
├── DashSync/iOS/ # iOS-specific code
├── DashSync/macOS/ # macOS-specific code
├── Example/ # Reference app and tests
├── Scripts/ # Build utilities
└── ChainResources/ # Blockchain data files
```
Comment thread
bfoss765 marked this conversation as resolved.

## Architecture

### Two-Layer Design
- **Layer 1 (Core)**: Traditional blockchain - transactions, blocks, masternodes
- **Layer 2 (Platform)**: Decentralized apps - identities, documents, contracts

### Model-Manager Pattern
- **Models**: Data structures (`DSChain`, `DSWallet`, `DSTransaction`)
- **Managers**: Service coordinators (`DSChainManager`, `DSPeerManager`)

### Key Managers
| Manager | Purpose |
|---------|---------|
| `DSChainsManager` | Multi-chain coordinator (singleton) |
| `DSChainManager` | Single chain operations |
| `DSPeerManager` | P2P network connectivity |
| `DSTransactionManager` | Transaction pool |
| `DSMasternodeManager` | Masternode lists & quorums |
| `DSIdentitiesManager` | Blockchain identities |
| `DSGovernanceSyncManager` | Governance data sync |

### Persistence
- **Core Data** with SQLite backend
- 83 entity definitions in `DashSync.xcdatamodeld`
- Custom transformers in `Models/Persistence/Transformers/`

## Code Conventions

### Naming
- All classes prefixed with `DS` (e.g., `DSChain`, `DSWallet`)
- Entities suffixed with `Entity` (e.g., `DSChainEntity`)
- Managers suffixed with `Manager` (e.g., `DSPeerManager`)

### File Organization
- Public headers in main directory
- `+Protected.h` files for subclass-accessible interfaces
- Categories in `Categories/` subdirectories

### Notifications
Event-driven via `NSNotificationCenter`:
- `DSChainBlocksDidFinishSyncingNotification`
- `DSWalletBalanceDidChangeNotification`
- `DSPeerManagerConnectedPeersDidChangeNotification`

## Key Classes

### Chain & Sync
- `DSChain` (3,562 lines) - Central blockchain state manager
- `DSBlock`, `DSMerkleBlock` - Block representations
- `DSChainLock` - Chain lock mechanism

### Wallet
- `DSWallet` - HD wallet management
- `DSAccount` - Account within wallet
- `DSBIP39Mnemonic` - Mnemonic seed handling
- `DSDerivationPath` - BIP32/44 key derivation

### Transactions
- `DSTransaction` - Base transaction class
- `DSCoinbaseTransaction` - Mining rewards
- `DSProviderRegistrationTransaction` - Masternode registration
- `DSQuorumCommitmentTransaction` - Quorum operations
- `DSCreditFundingTransaction` - Platform funding

### Identity & Platform
- `DSBlockchainIdentity` - Dash Platform identity
- `DSBlockchainInvitation` - Contact requests
- `DPContract` - Platform smart contracts
- `DPDocument` - Platform documents

### Privacy
- `DSCoinJoinManager` - CoinJoin mixing coordination
- `DSCoinJoinWrapper` - Protocol implementation

## Network Support

| Network | Purpose |
|---------|---------|
| Mainnet | Production Dash network |
| Testnet | Testing environment |
| Devnet | Development chains |
| Regnet | Local regression testing |

## Testing

Tests located in `Example/Tests/`:
- `DSChainTests.m` - Chain operations
- `DSTransactionTests.m` - Transaction handling
- `DSDeterministicMasternodeListTests.m` - Masternode lists
- `DSCoinJoinSessionTest.m` - Privacy mixing
- `DSDIP14Tests.m` - DIP compliance

## CI/CD Workflows

- `build.yml` - Main CI pipeline
- `test.yml` - Unit tests
- `lint.yml` - Code linting
- `coverage.yml` - Code coverage
- `syncTestMainnet.yml` / `syncTestTestnet.yml` - Network sync tests

## Dependencies

Key CocoaPods:
- **DashSharedCore** - Rust-based cryptographic primitives
- **CocoaLumberjack** - Logging framework
- **DAPI-GRPC** - Decentralized API protocol
- **TinyCborObjc** - CBOR serialization

## Localization

Supports 15+ languages: en, de, es, ja, zh-Hans, zh-Hant-TW, uk, bg, el, it, cs, sk, ko, pl, tr, vi

## Development Workflow

### Commit Policy
- **DO NOT commit changes until the user has tested them**
- Wait for explicit approval before creating commits
- This applies to all code changes, especially logging and behavioral modifications

### Related Repositories
- **DashJ** (Android equivalent): https://github.com/dashpay/dashj
- **Dash Wallet Android**: https://github.com/dashpay/dash-wallet

## External Resources

- [Dash Core Specs](https://dashcore.readme.io/docs)
- [Dash Improvement Proposals](https://github.com/dashpay/dips)
- [Developer Discord](https://discord.com/channels/484546513507188745/614505310593351735)
11 changes: 0 additions & 11 deletions DashSync/shared/Categories/NSData/NSData+Dash.m
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,13 @@ BOOL setKeychainData(NSData *data, NSString *key, BOOL authenticated) {
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)item, NULL);

if (status == noErr) return YES;
DSLogPrivate(@"SecItemAdd error: %@", [NSError osStatusErrorWithCode:status].localizedDescription);
return NO;
}

if (!data) {
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);

if (status == noErr) return YES;
DSLogPrivate(@"SecItemDelete error: %@", [NSError osStatusErrorWithCode:status].localizedDescription);
return NO;
}

Expand All @@ -72,7 +70,6 @@ BOOL setKeychainData(NSData *data, NSString *key, BOOL authenticated) {
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);

if (status == noErr) return YES;
DSLogPrivate(@"SecItemUpdate error: %@", [NSError osStatusErrorWithCode:status].localizedDescription);
return NO;
}

Expand All @@ -89,7 +86,6 @@ BOOL hasKeychainData(NSString *key, NSError **error) {

if (status == errSecItemNotFound) return NO;
if (status == noErr) return YES;
DSLogPrivate(@"SecItemCopyMatching error: %@", [NSError osStatusErrorWithCode:status].localizedDescription);
if (error) *error = [NSError osStatusErrorWithCode:status];
return NO;
}
Expand All @@ -104,7 +100,6 @@ BOOL hasKeychainData(NSString *key, NSError **error) {

if (status == errSecItemNotFound) return nil;
if (status == noErr) return CFBridgingRelease(result);
DSLogPrivate(@"SecItemCopyMatching error: %@", [NSError osStatusErrorWithCode:status].localizedDescription);
if (error) *error = [NSError osStatusErrorWithCode:status];
return nil;
}
Expand Down Expand Up @@ -164,9 +159,6 @@ BOOL setKeychainDict(NSDictionary *dict, NSString *key, BOOL authenticated) {
]];
set = [set setByAddingObjectsFromArray:classes];
NSDictionary *dictionary = [NSKeyedUnarchiver unarchivedObjectOfClasses:set fromData:d error:error];
if (*error) {
DSLogPrivate(@"error retrieving dictionary from keychain %@", *error);
}
return dictionary;
//}
}
Expand All @@ -189,9 +181,6 @@ BOOL setKeychainArray(NSArray *array, NSString *key, BOOL authenticated) {
]];
set = [set setByAddingObjectsFromArray:classes];
NSArray *array = [NSKeyedUnarchiver unarchivedObjectOfClasses:set fromData:d error:error];
if (*error) {
DSLogPrivate(@"error retrieving array from keychain %@", *error);
}
return array;
}
}
Expand Down
2 changes: 0 additions & 2 deletions DashSync/shared/Categories/NSManagedObject+Sugar.m
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ + (NSArray *)fetchObjects:(NSFetchRequest *)request inContext:(NSManagedObjectCo

[context performBlockAndWait:^{
a = [context executeFetchRequest:request error:&error];
if (error) DSLog(@"%s: %@", __func__, error);
}];

return a;
Expand Down Expand Up @@ -277,7 +276,6 @@ + (NSUInteger)countObjects:(NSFetchRequest *)request inContext:(NSManagedObjectC

[context performBlockAndWait:^{
count = [context countForFetchRequest:request error:&error];
if (error) DSLog(@"%s: %@", __func__, error);
}];

return count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ - (NSError *)ds_save {
#endif
NSError *error = nil;
if (![self save:&error]) { // persist changes
DSLog(@"%s: %@", __func__, error);
#if DEBUG
abort();
#endif
Expand Down
12 changes: 0 additions & 12 deletions DashSync/shared/DashSync.m
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ - (void)wipeBlockchainNonTerminalDataForChain:(DSChain *)chain inContext:(NSMana

- (void)wipeMasternodeDataForChain:(DSChain *)chain inContext:(NSManagedObjectContext *)context {
NSParameterAssert(chain);
DSLog(@"wipeMasternodeDataForChain: %@", chain);
[self stopSyncForChain:chain];
[context performBlockAndWait:^{
DSChainEntity *chainEntity = [chain chainEntityInContext:context];
Expand Down Expand Up @@ -317,16 +316,11 @@ - (void)scheduleBackgroundFetch {

NSError *error = nil;
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
if (error) {
DSLog(@"Error scheduling background refresh");
}
}

- (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
DSChainManager *mainnetManager = [[DSChainsManager sharedInstance] mainnetManager];
if (mainnetManager.syncState.chainSyncProgress >= 1.0) {
DSLog(@"Background fetch: already synced");

if (completionHandler) {
completionHandler(UIBackgroundFetchResultNoData);
}
Expand All @@ -343,7 +337,6 @@ - (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))com
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
DSLog(@"Background fetch: protected data available");
[[[DSChainsManager sharedInstance] mainnetManager] startSync];
}];

Expand All @@ -352,7 +345,6 @@ - (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))com
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
DSLog(@"Background fetch: sync finished");
[self finishBackgroundFetchWithResult:UIBackgroundFetchResultNewData];
}];

Expand All @@ -361,11 +353,9 @@ - (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))com
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
DSLog(@"Background fetch: sync failed");
[self finishBackgroundFetchWithResult:UIBackgroundFetchResultFailed];
}];

DSLog(@"Background fetch: starting");
[mainnetManager startSync];

// sync events to the server
Expand All @@ -374,8 +364,6 @@ - (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))com

- (void)backgroundFetchTimedOut {
const double syncProgress = [[DSChainsManager sharedInstance] mainnetManager].syncState.chainSyncProgress;
DSLog(@"Background fetch timeout with progress: %f", syncProgress);

const UIBackgroundFetchResult fetchResult = syncProgress > 0.1 ? UIBackgroundFetchResultNewData : UIBackgroundFetchResultFailed;
[self finishBackgroundFetchWithResult:fetchResult];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ - (void)start {
their readiness state.
*/
- (void)execute {
DSLog(@"%@ must override `execute`.", NSStringFromClass(self.class));
[self finish];
}

Expand Down
36 changes: 32 additions & 4 deletions DashSync/shared/Libraries/DSLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,56 @@ static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
static const DDLogLevel ddLogLevel = DDLogLevelInfo;
#endif /* DEBUG */

#define DSLog(frmt, ...) DDLogInfo(frmt, ##__VA_ARGS__) //!OCLINT
NS_ASSUME_NONNULL_BEGIN

// Thread name helper
NSString *DSCurrentThreadName(void);

#pragma mark - Android-style logging macros
// Format: "HH:MM:SS [thread] ClassName - message"
// These match the Android/DashJ log format

#define DSLogInfo(className, frmt, ...) DDLogInfo(@"[%@] %@ - " frmt, DSCurrentThreadName(), className, ##__VA_ARGS__)
#define DSLogDebug(className, frmt, ...) DDLogDebug(@"[%@] %@ - " frmt, DSCurrentThreadName(), className, ##__VA_ARGS__)
#define DSLogWarn(className, frmt, ...) DDLogWarn(@"[%@] %@ - " frmt, DSCurrentThreadName(), className, ##__VA_ARGS__)
#define DSLogError(className, frmt, ...) DDLogError(@"[%@] %@ - " frmt, DSCurrentThreadName(), className, ##__VA_ARGS__)

#ifdef DEBUG
#define DSLogVerbose(className, frmt, ...) DDLogVerbose(@"[%@] %@ - " frmt, DSCurrentThreadName(), className, ##__VA_ARGS__)
#else
#define DSLogVerbose(className, frmt, ...)
#endif /* DEBUG */

#pragma mark - Legacy logging macros (deprecated - for backward compatibility during migration)
// These will be removed after full migration to Android-style logging

#define DSLog(frmt, ...) DDLogInfo(frmt, ##__VA_ARGS__)

#ifdef DEBUG
#define DSLogPrivate(s, ...) DDLogVerbose(s, ##__VA_ARGS__)
#else
#define DSLogPrivate(s, ...)
#endif /* DEBUG */

NS_ASSUME_NONNULL_BEGIN

@interface DSLogger : NSObject

+ (instancetype)sharedInstance;

- (NSArray<NSURL *> *)logFiles;

/** @fn log:
* @brief This method is identical to `DSLog` macro
* @brief This method logs a message with default class name
* @param message Final message to log
*/
+ (void)log:(NSString *)message;

/** @fn log:className:
* @brief This method logs a message with specified class name
* @param message Final message to log
* @param className The class name to include in the log
*/
+ (void)log:(NSString *)message className:(NSString *)className;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

Expand Down
Loading
Loading