diff --git a/README.md b/README.md index fb17727..ab2f868 100644 --- a/README.md +++ b/README.md @@ -1,289 +1,5 @@ -# CircleCare - Next Generation Care Circles on Stacks +# CircleCare -**A complete rebuild leveraging Clarity 4 features for enhanced security, performance, and user experience** +A healthcare platform on Stacks. -[![Clarity Version](https://img.shields.io/badge/Clarity-4.0-blue.svg)](https://docs.stacks.co/whats-new/clarity-4-is-now-live) -[![Stacks](https://img.shields.io/badge/Built%20on-Stacks-5546FF.svg)](https://www.stacks.co/) -[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) - -## Overview - -CircleCare transforms expense sharing from tracking debts into flowing care. Instead of "you owe Alice $50," we recognize that communities operate in **circles of mutual care**—everyone contributes when they can, everyone receives when they need, and support comes full circle. - -This is a **ground-up rebuild** of CircleCare, architected from the start to leverage **Clarity 4's powerful new features** including: -- `contract-hash?` for contract integrity verification -- `restrict-assets?` for enhanced asset protection -- `to-ascii?` for improved data serialization -- `stacks-block-time` for time-based logic -- `secp256r1-verify` for passkey authentication readiness - -## Why This Rebuild? - -The original CircleCare prototype demonstrated the concept. This rebuild takes it to production-ready standards: - -### Smart Contract Improvements (Clarity 4) -- **Enhanced Security**: Leveraging `restrict-assets?` to prevent unauthorized asset movements -- **Contract Verification**: Using `contract-hash?` to ensure contract integrity -- **Time-Based Features**: Implementing `stacks-block-time` for expiring expenses and automated settlements -- **Better Data Handling**: Using `to-ascii?` for improved serialization and cross-chain readiness -- **Future-Ready Auth**: Foundation laid for `secp256r1-verify` passkey authentication - -### Frontend Modernization -- **Complete UI Redesign**: Fresh color palette while maintaining CircleCare's warm, flowing philosophy -- **Enhanced UX**: Improved wallet connection, transaction feedback, and error handling -- **Performance Optimizations**: Better state management and data fetching strategies -- **Accessibility**: WCAG 2.1 AA compliance from the ground up -- **Mobile-First**: Responsive design optimized for all device sizes - -## Project Structure - -``` -circlecare/ -├── contracts/ # Clarity 4 smart contracts -│ ├── contracts/ -│ │ ├── circle-factory.clar # Circle creation and management -│ │ └── circle-treasury.clar # Expense and settlement logic -│ ├── tests/ # Comprehensive contract tests -│ └── Clarinet.toml # Clarity 4 configuration -│ -├── frontend/ # Next.js 15 application -│ ├── app/ # App router pages -│ ├── components/ # Reusable UI components -│ ├── lib/ # Contract interactions & utilities -│ └── types/ # TypeScript definitions -│ -└── docs/ # Documentation - ├── ARCHITECTURE.md # Technical architecture - ├── CLARITY_4_FEATURES.md # How we use Clarity 4 - └── DEPLOYMENT.md # Deployment guide -``` - -## Tech Stack - -### Smart Contracts -- **Clarity 4**: Latest version with enhanced features -- **Clarinet 2.x**: Development and testing framework -- **Vitest**: Unit testing with clarinet-sdk - -### Frontend -- **Next.js 15**: React framework with App Router -- **React 19**: Latest React features -- **TypeScript 5.x**: Type safety throughout -- **Tailwind CSS 4**: Modern utility-first CSS -- **Stacks.js**: Web3 integration -- **TanStack Query**: Data fetching and caching - -## Getting Started - -### Prerequisites - -- Node.js 18+ and npm/yarn -- Clarinet 2.x ([Installation Guide](https://docs.hiro.so/clarinet)) -- A Stacks wallet (Leather, Xverse, or compatible) -- STX on testnet ([Get from faucet](https://explorer.hiro.so/sandbox/faucet)) - -### Installation - -1. **Clone the repository** - ```bash - git clone https://github.com/ThinkLikeAFounder/circlecare.git - cd circlecare - ``` - -2. **Install contract dependencies** - ```bash - cd contracts - npm install - ``` - -3. **Install frontend dependencies** - ```bash - cd ../frontend - npm install - ``` - -### Development - -**Smart Contracts** -```bash -cd contracts -clarinet test # Run contract tests -clarinet console # Interactive REPL -clarinet check # Syntax checking -``` - -**Frontend** -```bash -cd frontend -npm run dev # Start development server -npm run build # Production build -npm run type-check # TypeScript validation -``` - -## Clarity 4 Feature Implementation - -### 1. Contract Integrity Verification (`contract-hash?`) -We use `contract-hash?` to verify that interacting contracts match expected implementations, preventing malicious contract substitution. - -### 2. Asset Protection (`restrict-assets?`) -Settlement operations are wrapped with `restrict-assets?` to ensure: -- Users can only transfer amounts they actually owe -- Contract funds remain protected during external calls -- Automatic rollback on unauthorized transfers - -### 3. Time-Based Logic (`stacks-block-time`) -Expenses can now have: -- Expiration timestamps for automatic resolution -- Time-locked settlements -- Scheduled recurring contributions - -### 4. Enhanced Serialization (`to-ascii?`) -Convert principals, amounts, and other data to ASCII for: -- Better logging and events -- Cross-chain message formatting -- Human-readable receipts - -## Key Features - -### For Users -- **Create Care Circles**: Organize your community -- **Track Contributions**: Transparent expense sharing -- **Settle Debts**: Simple STX transfers -- **Time-Based Expenses**: Set expiration dates -- **Verified Contracts**: Know you're interacting with authentic code - -### For Developers -- **Clarity 4 Native**: Built for the latest Clarity features -- **Comprehensive Tests**: Full test coverage -- **Type-Safe**: TypeScript throughout the stack -- **Well-Documented**: Clear code and documentation -- **Extensible**: Easy to build on top of - -## Testing - -### Smart Contracts -```bash -cd contracts -npm test # Run all tests -npm run test:watch # Watch mode -npm run test:coverage # Coverage report -``` - -### Frontend -```bash -cd frontend -npm run test # Unit tests -npm run test:e2e # End-to-end tests -``` - -## Deployment - -### Smart Contracts -See [DEPLOYMENT.md](docs/DEPLOYMENT.md) for detailed instructions. - -```bash -cd contracts -clarinet deployments generate --testnet -clarinet deployments apply --testnet -``` - -### Frontend -Deploy to Vercel, Netlify, or any static hosting: -```bash -cd frontend -npm run build -``` - -## Documentation - -Comprehensive documentation is available in the `/docs` directory: - -### 📚 Complete Documentation -- **[Documentation Hub](docs/README.md)** - Start here for all documentation - -### 🚀 User Resources -- **[User Guide](docs/USER_GUIDE.md)** - Complete usage instructions -- **[FAQ](docs/FAQ.md)** - Frequently asked questions -- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common issues and solutions - -### 🏗️ Developer Resources -- **[Architecture](docs/ARCHITECTURE.md)** - System architecture and design -- **[API Reference](docs/API.md)** - Complete contract API documentation -- **[Clarity 4 Features](docs/CLARITY_4_FEATURES.md)** - How we leverage Clarity 4 -- **[Contributing](docs/CONTRIBUTING.md)** - Development guidelines and standards -- **[Deployment](docs/DEPLOYMENT.md)** - Deployment and infrastructure guide - -### 🔧 Technical Resources -- **[TypeScript Types](types/contracts.ts)** - Complete type definitions -- **[Contract Tests](contracts/tests/)** - Comprehensive test suite -- **[Contract Documentation](contracts/contracts/)** - Inline code documentation - -## Contributing - -We welcome contributions! Please see our [Contributing Guide](docs/CONTRIBUTING.md) for detailed guidelines: - -1. Check existing issues or create a new one -2. Fork the repository -3. Create a feature branch -4. Make your changes with tests -5. Submit a pull request - -For quick contributions: -- 🐛 **Bug Reports**: Use GitHub Issues -- 💡 **Feature Requests**: Create detailed GitHub Issues -- 📝 **Documentation**: Improve our docs -- 🔧 **Code**: Follow our development guidelines - -## Roadmap - -### Phase 1: Foundation (Current) -- [ ] Core Clarity 4 contracts with all new features -- [ ] Modern frontend with improved UX -- [ ] Comprehensive test coverage -- [ ] Documentation - -### Phase 2: Enhanced Features -- [ ] Recurring contributions -- [ ] Multi-token support (sBTC, other SIP-010 tokens) -- [ ] Passkey authentication using `secp256r1-verify` -- [ ] Mobile app (React Native) - -### Phase 3: Advanced -- [ ] Cross-chain bridges -- [ ] DAO governance -- [ ] Analytics dashboard -- [ ] API for third-party integrations - -## License - -MIT License - see [LICENSE](LICENSE) file for details - -## Acknowledgments - -- Built on [Stacks](https://www.stacks.co/) - Bitcoin Layer 2 -- Powered by [Clarity 4](https://docs.stacks.co/whats-new/clarity-4-is-now-live) -- Developed for [Talent Protocol Builder Challenge](https://talentprotocol.com/) - -## Support & Community - -### 📖 Documentation & Help -- **[Complete Documentation](docs/README.md)** - All guides and references -- **[User Guide](docs/USER_GUIDE.md)** - Get started with CircleCare -- **[FAQ](docs/FAQ.md)** - Quick answers to common questions -- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Solve issues quickly - -### 🤝 Community & Support -- **[Discord](https://discord.gg/circlecare)** - Real-time community support -- **[GitHub Issues](https://github.com/ThinkLikeAFounder/circlecare/issues)** - Bug reports and feature requests -- **[GitHub Discussions](https://github.com/ThinkLikeAFounder/circlecare/discussions)** - Community discussions -- **[Twitter @CircleCare_xyz](https://twitter.com/circlecare_xyz)** - Updates and announcements - -### 🛠️ Developer Resources -- **[API Documentation](docs/API.md)** - Contract interfaces and examples -- **[Architecture Guide](docs/ARCHITECTURE.md)** - System design and components -- **[Contributing Guide](docs/CONTRIBUTING.md)** - How to contribute code -- **[Deployment Guide](docs/DEPLOYMENT.md)** - Setup and deployment instructions - ---- - -**CircleCare** - Where care comes full circle, powered by Clarity 4 on Stacks +npm install diff --git a/contracts/contracts/circle-treasury.clar b/contracts/contracts/circle-treasury.clar index 6e24f17..12148e3 100644 --- a/contracts/contracts/circle-treasury.clar +++ b/contracts/contracts/circle-treasury.clar @@ -527,3 +527,106 @@ false ) ) + +;; Bulk Operations + +;; Add multiple expenses in one transaction (up to 10) +(define-private (add-expense-internal + (expense-data {description: (string-ascii 200), amount: uint, participants: (list 20 principal)})) + (let + ( + (circle-id (get circle-id expense-data)) + (description (get description expense-data)) + (amount (get amount expense-data)) + (participants (get participants expense-data)) + ) + ;; Call the main add-expense function + (add-expense circle-id description amount participants) + ) +) + +;; Add multiple expenses in one transaction +(define-public (add-multiple-expenses + (circle-id uint) + (expenses (list 10 {description: (string-ascii 200), amount: uint, participants: (list 20 principal)}))) + (let + ( + (circle (unwrap! (map-get? circles circle-id) ERR-CIRCLE-NOT-FOUND)) + (member-key {circle-id: circle-id, member: tx-sender}) + ) + ;; Basic validations + (asserts! (not (get paused circle)) ERR-CIRCLE-PAUSED) + (asserts! (get active (unwrap! (map-get? members member-key) ERR-MEMBER-NOT-FOUND)) ERR-UNAUTHORIZED) + (asserts! (> (len expenses) u0) ERR-INVALID-AMOUNT) + + ;; Process all expenses - each will validate individually + (ok (map add-expense-internal + (map (lambda (expense) (merge expense {circle-id: circle-id})) expenses))) + ) +) + +;; Settle multiple debts in one transaction (up to 10) +(define-private (settle-debt-internal (settlement-data {creditor: principal})) + (let + ( + (circle-id (get circle-id settlement-data)) + (creditor (get creditor settlement-data)) + ) + ;; Call the main settle function + (settle-debt-stx circle-id creditor) + ) +) + +;; Settle multiple debts in one transaction +(define-public (settle-multiple-debts + (circle-id uint) + (settlements (list 10 {creditor: principal}))) + (let + ( + (circle (unwrap! (map-get? circles circle-id) ERR-CIRCLE-NOT-FOUND)) + (member-key {circle-id: circle-id, member: tx-sender}) + ) + ;; Basic validations + (asserts! (not (get paused circle)) ERR-CIRCLE-PAUSED) + (asserts! (get active (unwrap! (map-get? members member-key) ERR-MEMBER-NOT-FOUND)) ERR-UNAUTHORIZED) + (asserts! (> (len settlements) u0) ERR-INVALID-AMOUNT) + + ;; Process all settlements - each will validate individually + (ok (map settle-debt-internal + (map (lambda (settlement) (merge settlement {circle-id: circle-id})) settlements))) + ) +) + +;; Bulk add members to circle (up to 10) +(define-private (add-member-internal + (member-data {member: principal, nickname: (string-ascii 32)})) + (let + ( + (circle-id (get circle-id member-data)) + (member (get member member-data)) + (nickname (get nickname member-data)) + ) + ;; Call the main add-member function + (add-member circle-id member nickname) + ) +) + +;; Add multiple members in one transaction +(define-public (add-multiple-members + (circle-id uint) + (new-members (list 10 {member: principal, nickname: (string-ascii 32)}))) + (let + ( + (circle (unwrap! (map-get? circles circle-id) ERR-CIRCLE-NOT-FOUND)) + ) + ;; Basic validations + (asserts! (is-eq tx-sender (get creator circle)) ERR-UNAUTHORIZED) + (asserts! (not (get paused circle)) ERR-CIRCLE-PAUSED) + (asserts! (> (len new-members) u0) ERR-INVALID-AMOUNT) + (asserts! (<= (+ (get member-count circle) (len new-members)) MAX-MEMBERS) ERR-MAX-MEMBERS) + + ;; Process all member additions - each will validate individually + (ok (map add-member-internal + (map (lambda (member-data) (merge member-data {circle-id: circle-id})) new-members))) + ) +) diff --git a/contracts/tests/circle-treasury.test.ts b/contracts/tests/circle-treasury.test.ts index 1ea8f01..fb75e56 100644 --- a/contracts/tests/circle-treasury.test.ts +++ b/contracts/tests/circle-treasury.test.ts @@ -696,4 +696,441 @@ describe("CircleCare Circle Treasury - Clarity 4 Tests", () => { expect(result).not.toBeNone(); }); }); + + describe("Bulk Operations", () => { + beforeEach(() => { + simnet.callPublicFn( + "circle-treasury", + "initialize-circle", + [ + Cl.uint(1), + Cl.stringAscii("Bulk Circle"), + Cl.principal(wallet1), + Cl.stringAscii("Creator") + ], + deployer + ); + + simnet.callPublicFn( + "circle-treasury", + "add-member", + [Cl.uint(1), Cl.principal(wallet2), Cl.stringAscii("Bob")], + wallet1 + ); + + simnet.callPublicFn( + "circle-treasury", + "add-member", + [Cl.uint(1), Cl.principal(wallet3), Cl.stringAscii("Charlie")], + wallet1 + ); + }); + + describe("Bulk Expense Addition", () => { + it("adds multiple expenses successfully", () => { + const expenses = [ + { + description: "Dinner", + amount: 300000000, // 300 STX + participants: [wallet1, wallet2, wallet3] + }, + { + description: "Lunch", + amount: 150000000, // 150 STX + participants: [wallet1, wallet2] + }, + { + description: "Coffee", + amount: 50000000, // 50 STX + participants: [wallet2, wallet3] + } + ]; + + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-expenses", + [ + Cl.uint(1), + Cl.list(expenses.map(exp => Cl.tuple({ + 'description': Cl.stringAscii(exp.description), + 'amount': Cl.uint(exp.amount), + 'participants': Cl.list(exp.participants.map(p => Cl.principal(p))) + }))) + ], + wallet1 + ); + + expect(result).toBeOk(Cl.list([ + Cl.uint(1), // First expense ID + Cl.uint(2), // Second expense ID + Cl.uint(3) // Third expense ID + ])); + }); + + it("validates expenses individually", () => { + const expenses = [ + { + description: "Valid", + amount: 100000000, + participants: [wallet1, wallet2] + }, + { + description: "Invalid Participants", + amount: 50000000, + participants: [wallet1, wallet4] // wallet4 not a member + } + ]; + + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-expenses", + [ + Cl.uint(1), + Cl.list(expenses.map(exp => Cl.tuple({ + 'description': Cl.stringAscii(exp.description), + 'amount': Cl.uint(exp.amount), + 'participants': Cl.list(exp.participants.map(p => Cl.principal(p))) + }))) + ], + wallet1 + ); + + expect(result).toBeErr(Cl.uint(202)); // ERR-INVALID-PARTICIPANT from second expense + }); + + it("requires active circle membership", () => { + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-expenses", + [ + Cl.uint(1), + Cl.list([Cl.tuple({ + 'description': Cl.stringAscii("Test"), + 'amount': Cl.uint(100000000), + 'participants': Cl.list([Cl.principal(wallet1)]) + })]) + ], + wallet3 // Not a member who can add expenses + ); + + expect(result).toBeErr(Cl.uint(200)); // ERR-UNAUTHORIZED + }); + + it("handles empty expense list", () => { + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-expenses", + [Cl.uint(1), Cl.list([])], + wallet1 + ); + + expect(result).toBeErr(Cl.uint(201)); // ERR-INVALID-AMOUNT (empty list) + }); + }); + + describe("Bulk Debt Settlement", () => { + beforeEach(() => { + // Create some expenses to generate debts + simnet.callPublicFn( + "circle-treasury", + "add-expense", + [ + Cl.uint(1), + Cl.stringAscii("Dinner"), + Cl.uint(300000000), + Cl.list([Cl.principal(wallet1), Cl.principal(wallet2), Cl.principal(wallet3)]) + ], + wallet1 + ); + + simnet.callPublicFn( + "circle-treasury", + "add-expense", + [ + Cl.uint(1), + Cl.stringAscii("Lunch"), + Cl.uint(200000000), + Cl.list([Cl.principal(wallet2), Cl.principal(wallet3)]) + ], + wallet2 + ); + }); + + it("settles multiple debts successfully", () => { + const settlements = [ + { creditor: wallet1 }, // wallet2 owes wallet1 from dinner + { creditor: wallet2 } // wallet3 owes wallet2 from lunch + ]; + + const { result } = simnet.callPublicFn( + "circle-treasury", + "settle-multiple-debts", + [ + Cl.uint(1), + Cl.list(settlements.map(s => Cl.tuple({ + 'creditor': Cl.principal(s.creditor) + }))) + ], + wallet2 // wallet2 settling debts + ); + + expect(result).toBeOk(Cl.list([ + Cl.uint(1), // First settlement ID + Cl.uint(2) // Second settlement ID + ])); + }); + + it("handles debts with no balance", () => { + const settlements = [ + { creditor: wallet1 }, // Valid debt + { creditor: wallet3 } // No debt between wallet2 and wallet3 + ]; + + const { result } = simnet.callPublicFn( + "circle-treasury", + "settle-multiple-debts", + [ + Cl.uint(1), + Cl.list(settlements.map(s => Cl.tuple({ + 'creditor': Cl.principal(s.creditor) + }))) + ], + wallet2 + ); + + expect(result).toBeErr(Cl.uint(205)); // ERR-NO-DEBT from second settlement + }); + + it("requires active membership", () => { + const { result } = simnet.callPublicFn( + "circle-treasury", + "settle-multiple-debts", + [ + Cl.uint(1), + Cl.list([Cl.tuple({ + 'creditor': Cl.principal(wallet1) + })]) + ], + wallet4 // Not a member + ); + + expect(result).toBeErr(Cl.uint(206)); // ERR-MEMBER-NOT-FOUND + }); + }); + + describe("Bulk Member Addition", () => { + it("adds multiple members successfully", () => { + const newMembers = [ + { member: wallet2, nickname: "Bob" }, + { member: wallet3, nickname: "Charlie" }, + { member: wallet4, nickname: "David" } + ]; + + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-members", + [ + Cl.uint(1), + Cl.list(newMembers.map(m => Cl.tuple({ + 'member': Cl.principal(m.member), + 'nickname': Cl.stringAscii(m.nickname) + }))) + ], + wallet1 // Creator + ); + + expect(result).toBeOk(Cl.list([ + Cl.bool(true), // First addition success + Cl.bool(true), // Second addition success + Cl.bool(true) // Third addition success + ])); + }); + + it("validates member limits", () => { + // Try to add more members than the limit allows + const manyMembers = Array.from({length: 48}, (_, i) => ({ + member: accounts.get(`wallet_${i + 5}`)!, // wallet_5 onwards + nickname: `User${i + 5}` + })); + + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-members", + [ + Cl.uint(1), + Cl.list(manyMembers.map(m => Cl.tuple({ + 'member': Cl.principal(m.member), + 'nickname': Cl.stringAscii(m.nickname) + }))) + ], + wallet1 + ); + + expect(result).toBeErr(Cl.uint(210)); // ERR-MAX-MEMBERS + }); + + it("requires creator authorization", () => { + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-members", + [ + Cl.uint(1), + Cl.list([Cl.tuple({ + 'member': Cl.principal(wallet4), + 'nickname': Cl.stringAscii("Hacker") + })]) + ], + wallet2 // Not creator + ); + + expect(result).toBeErr(Cl.uint(200)); // ERR-UNAUTHORIZED + }); + + it("handles duplicate members in bulk", () => { + // Add wallet2 first + simnet.callPublicFn( + "circle-treasury", + "add-member", + [Cl.uint(1), Cl.principal(wallet2), Cl.stringAscii("Bob")], + wallet1 + ); + + // Try to add wallet2 again in bulk + const { result } = simnet.callPublicFn( + "circle-treasury", + "add-multiple-members", + [ + Cl.uint(1), + Cl.list([Cl.tuple({ + 'member': Cl.principal(wallet2), + 'nickname': Cl.stringAscii("Bob2") + })]) + ], + wallet1 + ); + + expect(result).toBeErr(Cl.uint(209)); // ERR-MEMBER-EXISTS + }); + }); + + describe("Bulk Operation Integration", () => { + it("combines bulk operations in complex workflow", () => { + // 1. Bulk add members + simnet.callPublicFn( + "circle-treasury", + "add-multiple-members", + [ + Cl.uint(1), + Cl.list([ + Cl.tuple({ + 'member': Cl.principal(wallet2), + 'nickname': Cl.stringAscii("Bob") + }), + Cl.tuple({ + 'member': Cl.principal(wallet3), + 'nickname': Cl.stringAscii("Charlie") + }), + Cl.tuple({ + 'member': Cl.principal(wallet4), + 'nickname': Cl.stringAscii("David") + }) + ]) + ], + wallet1 + ); + + // 2. Bulk add expenses + simnet.callPublicFn( + "circle-treasury", + "add-multiple-expenses", + [ + Cl.uint(1), + Cl.list([ + Cl.tuple({ + 'description': Cl.stringAscii("Dinner"), + 'amount': Cl.uint(400000000), + 'participants': Cl.list([Cl.principal(wallet1), Cl.principal(wallet2), Cl.principal(wallet3), Cl.principal(wallet4)]) + }), + Cl.tuple({ + 'description': Cl.stringAscii("Drinks"), + 'amount': Cl.uint(100000000), + 'participants': Cl.list([Cl.principal(wallet2), Cl.principal(wallet3), Cl.principal(wallet4)]) + }) + ]) + ], + wallet1 + ); + + // 3. Bulk settle debts + simnet.callPublicFn( + "circle-treasury", + "settle-multiple-debts", + [ + Cl.uint(1), + Cl.list([ + Cl.tuple({'creditor': Cl.principal(wallet1)}), // wallet2 settles with wallet1 + Cl.tuple({'creditor': Cl.principal(wallet1)}) // wallet3 settles with wallet1 + ]) + ], + wallet2 + ); + + // Verify balances are updated + const balance1 = simnet.callReadOnlyFn( + "circle-treasury", + "get-balance", + [Cl.uint(1), Cl.principal(wallet2), Cl.principal(wallet1)], + wallet1 + ); + expect(balance1.result).toBeUint(0); // Debt settled + + const balance2 = simnet.callReadOnlyFn( + "circle-treasury", + "get-balance", + [Cl.uint(1), Cl.principal(wallet3), Cl.principal(wallet1)], + wallet1 + ); + expect(balance2.result).toBeUint(0); // Debt settled + }); + + it("handles paused circle for bulk operations", () => { + // Pause the circle + simnet.callPublicFn( + "circle-treasury", + "pause-circle", + [Cl.uint(1)], + wallet1 + ); + + // Try bulk operations + const expenseResult = simnet.callPublicFn( + "circle-treasury", + "add-multiple-expenses", + [ + Cl.uint(1), + Cl.list([Cl.tuple({ + 'description': Cl.stringAscii("Test"), + 'amount': Cl.uint(100000000), + 'participants': Cl.list([Cl.principal(wallet1)]) + })]) + ], + wallet1 + ); + + expect(expenseResult.result).toBeErr(Cl.uint(208)); // ERR-CIRCLE-PAUSED + + const settlementResult = simnet.callPublicFn( + "circle-treasury", + "settle-multiple-debts", + [ + Cl.uint(1), + Cl.list([Cl.tuple({'creditor': Cl.principal(wallet1)})]) + ], + wallet2 + ); + + expect(settlementResult.result).toBeErr(Cl.uint(208)); // ERR-CIRCLE-PAUSED + }); + }); + }); });