diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..889ca8f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## Bug Description + +A clear and concise description of the bug. + +## Steps to Reproduce + +1. Go to '...' +2. Click on '...' +3. Scroll down to '...' +4. See error + +## Expected Behavior + +A clear description of what you expected to happen. + +## Actual Behavior + +A clear description of what actually happens. + +## Screenshots + +If applicable, add screenshots to help explain your problem. + +## Environment + +- **Operating System**: [e.g. Windows 11, macOS 14, Ubuntu 22.04] +- **Browser**: [e.g. Chrome 120, Firefox 121, Safari 17] +- **Node.js Version**: [e.g. 18.17.0] +- **Project Version**: [e.g. 1.0.0] + +## Additional Information + +Any other context about the problem. + +## Policy Verification + +- [ ] I have reviewed that this issue complies with [GitHub Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) +- [ ] It does not contain sensitive or personal information +- [ ] It does not promote illegal activities or inappropriate content diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a5913fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,45 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Is your feature request related to a problem? + +A clear and concise description of the problem. E.g. I'm frustrated when [...] + +## Describe the solution you'd like + +A clear and concise description of what you want to happen. + +## Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +## Use Cases + +Describe how this feature would be used: +- Use case 1: ... +- Use case 2: ... + +## Expected Impact + +- How would this improve the user experience? +- How many users would benefit? +- Is this a critical improvement or nice-to-have? + +## Mockups/Designs + +If you have mockups or designs, share them here. + +## Additional Information + +Any other context or screenshots about the feature request. + +## Policy Verification + +- [ ] I have reviewed that this request complies with [GitHub Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) +- [ ] It does not contain inappropriate content or copyright violations +- [ ] It is a legitimate and constructive request diff --git a/.github/ISSUE_TEMPLATE/security.md b/.github/ISSUE_TEMPLATE/security.md new file mode 100644 index 0000000..9355fd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security.md @@ -0,0 +1,42 @@ +--- +name: Security Report +about: Report a security vulnerability (DO NOT use for public issues) +title: '[SECURITY] ' +labels: security +assignees: '' +--- + +## ⚠️ IMPORTANT: Do not publish vulnerabilities here + +**Please DO NOT report security vulnerabilities publicly.** + +Instead: +1. Send an email to [INSERT SECURITY EMAIL] +2. Or create a private [Security Advisory](https://github.com/vaiosx01/Gigstream/security/advisories/new) + +Read our [Security Policy](SECURITY.md) for more information. + +## Vulnerability Description + +[FOR INTERNAL USE ONLY - DO NOT complete in public issues] + +## Severity + +- [ ] Critical +- [ ] High +- [ ] Medium +- [ ] Low + +## Steps to Reproduce + +1. ... +2. ... +3. ... + +## Impact + +Describe the potential impact of this vulnerability. + +## Proposed Solution + +If you have suggestions for fixing the issue. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..9040878 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,61 @@ +## Description + +Briefly describe the changes in this PR. + +## Type of Change + +- [ ] Bug fix (change that fixes an issue) +- [ ] New feature (change that adds functionality) +- [ ] Breaking change (change that breaks compatibility) +- [ ] Documentation (changes only in documentation) +- [ ] Refactoring (code change that neither fixes a bug nor adds functionality) +- [ ] Performance improvement +- [ ] Test (add or fix tests) + +## Checklist + +- [ ] My code follows the project's style standards +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## GitHub Policies Verification + +- [ ] I have reviewed [GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) +- [ ] I have reviewed [GitHub Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) +- [ ] **I have NOT** included sensitive information (API keys, passwords, private tokens) +- [ ] **I have NOT** included content that violates copyright +- [ ] **I have NOT** included malicious code or spam +- [ ] My code complies with all GitHub policies + +## Additional Information + +Any additional information reviewers should know. + +## Screenshots (if applicable) + +If your changes affect the UI, include screenshots. + +## Tests + +Describe the tests you performed to verify your changes. + +## Security Checklist + +- [ ] No API keys, passwords, or tokens in code +- [ ] No personal user information +- [ ] Environment variables are correctly configured +- [ ] Smart contracts have been security reviewed (if applicable) + +## Related Links + +- Related issue: # +- Related documentation: [link] + +--- + +**Note**: This PR will be reviewed according to GitHub policies and project standards. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2329d75 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +# Dependabot configuration to keep dependencies updated +# Complies with GitHub security policies + +version: 2 +updates: + # Enable updates for npm/pnpm + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "automated" + commit-message: + prefix: "chore" + include: "scope" + # Ignore major updates for critical dependencies + ignore: + - dependency-name: "next" + update-types: ["version-update:semver-major"] + - dependency-name: "react" + update-types: ["version-update:semver-major"] + - dependency-name: "react-dom" + update-types: ["version-update:semver-major"] + - dependency-name: "@somnia-chain/streams" + update-types: ["version-update:semver-major"] + + # Updates for contracts (if there's a separate package.json) + - package-ecosystem: "npm" + directory: "/contracts" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "contracts" + - "automated" diff --git a/.github/workflows/compliance-check.yml b/.github/workflows/compliance-check.yml new file mode 100644 index 0000000..fb34780 --- /dev/null +++ b/.github/workflows/compliance-check.yml @@ -0,0 +1,105 @@ +# Workflow to verify compliance with GitHub policies +# Runs automatic checks before merging PRs + +name: Compliance Verification + +on: + pull_request: + branches: [ main, develop ] + push: + branches: [ main, develop ] + +jobs: + security-check: + name: Security Verification + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Verify sensitive information + run: | + echo "Verifying no sensitive information in code..." + # Check for hardcoded API keys + if grep -r "api[_-]key" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" src/ contracts/; then + echo "⚠️ WARNING: Possible API keys found in code" + echo "Please ensure you use environment variables" + fi + + # Check for passwords + if grep -ri "password\s*=\s*['\"].*['\"]" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" src/ contracts/; then + echo "❌ ERROR: Hardcoded passwords found" + exit 1 + fi + + # Check for private keys + if grep -ri "private[_-]key\s*=\s*['\"].*['\"]" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" src/ contracts/; then + echo "❌ ERROR: Private keys found in code" + exit 1 + fi + + echo "✅ Security verification completed" + + - name: Verify .env.local is not committed + run: | + if git ls-files | grep -q "\.env\.local"; then + echo "❌ ERROR: .env.local should not be in the repository" + exit 1 + fi + echo "✅ .env.local is not in the repository" + + code-quality: + name: Code Quality + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'pnpm' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run linter + run: pnpm run lint + + - name: Verify types + run: pnpm run type-check + + policy-compliance: + name: Policy Compliance + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Verify policy files + run: | + echo "Verifying required policy files exist..." + required_files=("CODE_OF_CONDUCT.md" "CONTRIBUTING.md" "SECURITY.md") + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + echo "❌ ERROR: Missing file $file" + exit 1 + fi + done + echo "✅ All policy files are present" + + - name: Verify README references policies + run: | + if ! grep -q "CODE_OF_CONDUCT\|CONTRIBUTING\|SECURITY" README.md; then + echo "⚠️ WARNING: README.md should reference policy files" + else + echo "✅ README.md references policies" + fi diff --git a/.gitignore b/.gitignore index 01ab233..c290e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -25,10 +25,35 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# Local env files +# Local env files (NEVER commit these!) +# IMPORTANT: Complies with GitHub security policies +# Do not commit files with sensitive information (API keys, passwords, tokens) .env*.local .env -env.example +.env.production +.env.development +.env.test + +# env.example SHOULD be versioned (it's a template) +# Remove env.example from ignore if you want to track it +# env.example + +# Files with sensitive information (GitHub Policies) +*.key +*.pem +*.p12 +*.pfx +*.jks +*.keystore +secrets/ +.secrets/ +*.secret +*secret* +private-keys/ +.private/ +credentials.json +config.json +secrets.json # Vercel .vercel diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..09ce2f7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,62 @@ +# Contributor Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL OR CONTACT METHOD]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## GitHub Policies + +This project complies with [GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) and [GitHub Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies). + +### Prohibited Activities + +In accordance with GitHub policies, the following activities are strictly prohibited: + +* **Illegal activities**: Content that promotes illegal activities is not allowed +* **Sexually explicit content**: Sexually explicit material or content related to exploitation is not allowed +* **False or misleading information**: Information that may negatively affect the public is not allowed +* **Spam**: Spam or cryptocurrency mining activities are not allowed +* **Harassment**: Harassment, abuse, or threats towards other users are not allowed +* **Discrimination**: Any form of discrimination is not allowed + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, +available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..19fd23d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,236 @@ +# Contributing Guide to GigStream + +Thank you for your interest in contributing to GigStream! This document provides guidelines for contributing to the project effectively and in alignment with GitHub policies. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) +- [Development Process](#development-process) +- [Code Standards](#code-standards) +- [GitHub Policies](#github-policies) +- [Questions](#questions) + +## Code of Conduct + +This project follows a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [INSERT EMAIL OR CONTACT METHOD]. + +## How Can I Contribute? + +### Reporting Bugs + +If you find a bug, please: + +1. **Check that the bug hasn't already been reported** by searching in [Issues](https://github.com/vaiosx01/Gigstream/issues) +2. If it doesn't exist, create a new issue with: + - A clear and descriptive title + - A detailed description of the problem + - Steps to reproduce the bug + - Expected behavior vs actual behavior + - Environment information (Node.js version, operating system, etc.) + - Screenshots if applicable + +### Suggesting Enhancements + +Suggestions for new features are welcome: + +1. **Check that the suggestion hasn't been proposed** by searching in [Issues](https://github.com/vaiosx01/Gigstream/issues) +2. Create a new issue with: + - A clear and descriptive title + - Detailed description of the proposed functionality + - Explanation of why it would be useful + - Usage examples if applicable + +### Contributing Code + +1. **Fork the repository** +2. **Create a branch** for your feature or fix: + ```bash + git checkout -b feature/your-feature-name + # or + git checkout -b fix/your-fix-description + ``` +3. **Make your changes** following the [Code Standards](#code-standards) +4. **Ensure tests pass**: + ```bash + pnpm test + pnpm run test:e2e + ``` +5. **Commit your changes** with descriptive messages: + ```bash + git commit -m "feat: add new feature X" + # or + git commit -m "fix: fix bug in component Y" + ``` +6. **Push to your fork**: + ```bash + git push origin feature/your-feature-name + ``` +7. **Open a Pull Request** in the main repository + +## Development Process + +### Environment Setup + +1. Clone your fork: + ```bash + git clone https://github.com/YOUR_USERNAME/Gigstream.git + cd Gigstream + ``` + +2. Install dependencies: + ```bash + pnpm install + ``` + +3. Configure environment variables: + ```bash + cp env.example .env.local + # Edit .env.local with your credentials + ``` + +4. Run the development server: + ```bash + pnpm run dev + ``` + +### Project Structure + +``` +Gigstream/ +├── src/ # Main source code +│ ├── app/ # Next.js app router +│ ├── components/ # React components +│ ├── hooks/ # Custom hooks +│ ├── lib/ # Utilities and libraries +│ └── providers/ # Context providers +├── contracts/ # Smart contracts (Solidity) +├── tests/ # E2E tests +└── public/ # Static files +``` + +## Code Standards + +### TypeScript + +- Use TypeScript for all new code +- Avoid `any` - use specific types +- Document complex functions with JSDoc + +### Code Style + +- Follow the existing project style +- Use Prettier for formatting (configured in the project) +- Run the linter before committing: + ```bash + pnpm run lint + ``` + +### Commits + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +- `feat:` New feature +- `fix:` Bug fix +- `docs:` Documentation changes +- `style:` Formatting changes (doesn't affect code) +- `refactor:` Code refactoring +- `test:` Add or modify tests +- `chore:` Tool or configuration changes + +Example: +``` +feat: add real-time notification system +fix: fix wallet connection error +docs: update README with new instructions +``` + +### Tests + +- Write tests for new features +- Ensure all tests pass before making a PR +- Maintain or improve existing test coverage + +### Smart Contracts + +- Follow Solidity security best practices +- Document all public functions +- Add tests for new contracts +- Verify contracts after deployment + +## GitHub Policies + +This project strictly complies with official GitHub policies. Please make sure to review and follow: + +### GitHub Community Guidelines + +- [Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) +- Maintain a respectful and inclusive environment +- We do not tolerate harassment, abuse, or discrimination + +### Acceptable Use Policies + +- [Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) +- **DO NOT** upload illegal content, spam, or malicious code +- **DO NOT** use the repository for cryptocurrency mining +- **DO NOT** publish personal information without consent +- **DO NOT** violate copyright or intellectual property rights + +### Security + +- [Security Policy](SECURITY.md) +- Report vulnerabilities responsibly +- Do not publish vulnerabilities publicly before they are resolved + +### Prohibited Content + +In accordance with GitHub policies, the following is strictly prohibited: + +- Content that promotes illegal activities +- Sexually explicit material +- False or misleading information +- Spam or platform abuse activities +- Content that violates copyright +- Sensitive data (API keys, passwords, private tokens) + +### Verification Before Pushing Changes + +Before committing or pushing, verify: + +- ✅ No API keys, passwords, or private tokens in the code +- ✅ No personal user information +- ✅ Code complies with acceptable use policies +- ✅ Commits have descriptive messages +- ✅ Tests pass +- ✅ Code is properly formatted + +## Pull Requests + +### Review Process + +1. PRs will be reviewed by maintainers +2. There may be requests for changes +3. Once approved, the PR will be merged + +### PR Checklist + +- [ ] Code follows project standards +- [ ] Tests pass locally +- [ ] Tests added for new features +- [ ] Documentation updated if necessary +- [ ] No sensitive information in code +- [ ] Commits follow Conventional Commits format +- [ ] PR has a clear description of changes + +## Questions + +If you have questions about how to contribute: + +1. Review existing documentation +2. Search existing issues +3. Open a new issue with the `question` label +4. Contact project maintainers + +## Acknowledgments + +Thank you for contributing to GigStream! Your help makes this project better for everyone. diff --git a/GITHUB_POLICIES.md b/GITHUB_POLICIES.md new file mode 100644 index 0000000..5257e8d --- /dev/null +++ b/GITHUB_POLICIES.md @@ -0,0 +1,131 @@ +# GitHub Policies Compliance + +This document summarizes how GigStream complies with official GitHub policies (December 2025). + +## 📋 Policies We Comply With + +### 1. GitHub Community Guidelines + +**Link**: [Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) + +**Commitments**: +- ✅ We maintain a respectful and inclusive environment +- ✅ We prohibit harassment, abuse, or discrimination +- ✅ We encourage constructive collaboration +- ✅ We respect different viewpoints + +**Related file**: [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) + +### 2. Acceptable Use Policies + +**Link**: [Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) + +**Prohibited Activities** (strictly enforced): + +- ❌ **Illegal content**: We do not allow content that promotes illegal activities +- ❌ **Sexually explicit content**: We do not allow sexually explicit material +- ❌ **False information**: We do not allow false or misleading information +- ❌ **Spam**: We do not allow spam or cryptocurrency mining +- ❌ **Harassment**: We do not allow harassment, abuse, or threats +- ❌ **Copyright violation**: We do not allow copyright infringement + +**Automatic Verifications**: +- ✅ GitHub Actions workflow verifies no sensitive information +- ✅ Dependabot keeps dependencies updated +- ✅ `.gitignore` protects sensitive files + +### 3. Security Policies + +**Link**: [Security Policies](https://docs.github.com/en/site-policy/security-policies) + +**Implemented Practices**: +- ✅ Coordinated vulnerability disclosure process +- ✅ Automatic verification of sensitive information in PRs +- ✅ Protection of `.env.local` files and credentials +- ✅ Security review of smart contracts + +**Related file**: [SECURITY.md](./SECURITY.md) + +## 🔒 Sensitive Information Protection + +### Files Protected by `.gitignore` + +``` +.env*.local # Local environment variables +.env # Environment files +*.key # Private keys +*.pem # Certificates +secrets/ # Secrets directory +credentials.json # Credentials +``` + +### Automatic Verifications + +The `.github/workflows/compliance-check.yml` workflow verifies: + +1. **Sensitive Information**: + - Hardcoded API keys + - Passwords in code + - Private keys + - Access tokens + +2. **Protected Files**: + - `.env.local` is not in the repository + - Secret files are not committed + +3. **Policy Compliance**: + - Policy files are present + - README references policies + +## 📝 Pre-Commit Checklist + +Before committing or pushing, verify: + +- [ ] ❌ **NO** API keys, passwords, or tokens in code +- [ ] ❌ **NO** personal user information +- [ ] ❌ **NO** content that violates copyright +- [ ] ❌ **NO** malicious code or spam +- [ ] ✅ Environment variables are in `.env.local` (not committed) +- [ ] ✅ `.env.local` is in `.gitignore` +- [ ] ✅ Commits have descriptive messages +- [ ] ✅ Code follows project standards + +## 🚨 What to Do If You Find Sensitive Information + +If you accidentally committed sensitive information: + +1. **DO NOT** commit the fix directly +2. **Rotate immediately** the compromised credentials +3. **Remove from history** using `git filter-branch` or `BFG Repo-Cleaner` +4. **Notify** project maintainers +5. **Review** the entire history to ensure no sensitive information remains + +## 📚 Additional Resources + +- [GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) +- [Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) +- [Security Policies](https://docs.github.com/en/site-policy/security-policies) +- [GitHub Security Best Practices](https://docs.github.com/en/code-security) +- [Removing sensitive data from a repository](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) + +## ✅ Continuous Verification + +This project implements continuous verifications to ensure compliance: + +- **GitHub Actions**: Verification workflow on each PR +- **Dependabot**: Automatic dependency updates +- **Pre-commit hooks**: Local verifications (recommended) +- **Code Review**: Manual review of all PRs + +## 📞 Contact + +For policy questions or to report violations: + +- **Issues**: Use templates in `.github/ISSUE_TEMPLATE/` +- **Security**: Read [SECURITY.md](./SECURITY.md) to report vulnerabilities +- **Contributions**: Read [CONTRIBUTING.md](./CONTRIBUTING.md) to contribute + +--- + +**Last updated**: December 2025 +**Complies with**: Official GitHub Policies (December 2025) diff --git a/INTEGRACION_SDS_VISUAL.md b/INTEGRACION_SDS_VISUAL.md new file mode 100644 index 0000000..01504ae --- /dev/null +++ b/INTEGRACION_SDS_VISUAL.md @@ -0,0 +1,94 @@ +# Integración Visual de Somnia Data Streams + +## ✅ Elementos Visuales Implementados + +### 1. **Dashboard Principal (`/gigstream`)** + +#### Header Section +- **Indicador SDS con contador** (línea 79-81) + - Se muestra al lado de "Live SDS Streams • X active jobs" + - Badge con icono de Database + contador "X in SDS" + - Solo visible si hay jobs en Data Streams + - Estilo: gradiente cyan/green con borde + +#### Live Streams Card +- **Indicador SDS pequeño** (línea 165-167) + - Icono sin contador en la esquina superior derecha + - Solo visible si hay jobs en SDS +- **Contador de jobs en SDS** (línea 170-174) + - Texto: "X job(s) in Data Streams" + - Color: somnia-cyan/80 + - Solo visible si hay jobs en SDS + +### 2. **JobCard Component** + +#### Badge de SDS +- **Icono Database** (línea 78-81) + - Se muestra en la esquina superior derecha del card + - Color: somnia-cyan + - Tooltip: "Available in Somnia Data Streams" + - Solo visible si el job está en Data Streams + - Verificación asíncrona al cargar el job + +### 3. **SDSJobsIndicator Component** + +#### Estados Visuales +1. **Loading** (línea 16-22) + - Spinner animado + - Texto "Loading SDS..." + - Color: white/50 + +2. **Empty** (línea 25-27) + - No se muestra nada (return null) + +3. **With Jobs** (línea 29-43) + - Badge con gradiente cyan/green + - Icono Database (cyan) + - Contador "X in SDS" (si showCount=true) + - Icono Zap animado (green, pulsing) + - Animación de entrada (fade + scale) + +## 🎨 Estilos Visuales + +### Colores +- **Somnia Cyan**: `text-somnia-cyan` / `border-somnia-cyan/30` +- **MX Green**: `text-mx-green` (para animación) +- **Gradientes**: `from-somnia-cyan/20 to-mx-green/20` + +### Animaciones +- **Fade In + Scale**: `initial={{ opacity: 0, scale: 0.9 }}` → `animate={{ opacity: 1, scale: 1 }}` +- **Pulse**: `animate-pulse` en icono Zap +- **Spinner**: `animate-spin` en estado loading + +## 📍 Ubicaciones en el Frontend + +1. **Dashboard Header** (`src/app/gigstream/page.tsx:79-81`) + - Indicador con contador visible + +2. **Live Streams Card** (`src/app/gigstream/page.tsx:165-174`) + - Indicador pequeño + contador de texto + +3. **Job Cards** (`src/components/gigstream/JobCard.tsx:78-81`) + - Badge individual por job + +## 🔄 Comportamiento + +### Condicional +- Los indicadores **solo se muestran** si: + - `sdsJobs.length > 0` (hay jobs en SDS) + - `isInSDS === true` (para JobCard individual) + +### Actualización +- Los datos se refrescan automáticamente: + - Hook `useSDSJobs` se ejecuta cuando cambia `address` o `isConnected` + - JobCard verifica SDS cuando se carga el job + +## 🚀 Para Ver los Indicadores + +1. **Conectar wallet** con jobs publicados en SDS +2. **Publicar un job** (se publica automáticamente a SDS) +3. **Ver indicadores** en: + - Header del dashboard + - Card "Live Streams" + - Cards individuales de jobs + diff --git a/README.md b/README.md index f608262..c9f87fc 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,37 @@
-# 🚀 GigStream MX +# 🚀 GigStream -### **The Future of Freelance Work is Here** 🌟 +### **Global Real-Time Freelance Marketplace Powered by Somnia Data Streams** 🌟 -[![Vercel](https://img.shields.io/badge/Vercel-Deployed-000000?style=for-the-badge&logo=vercel&logoColor=white)](https://gigstream-mx.vercel.app) +[![Vercel](https://img.shields.io/badge/Vercel-Deployed-000000?style=for-the-badge&logo=vercel&logoColor=white)](https://gigstream-5ijgucloh-vaiosxs-projects.vercel.app) [![Somnia Testnet](https://img.shields.io/badge/Somnia-Testnet-7C3AED?style=for-the-badge&logo=ethereum&logoColor=white)](https://shannon-explorer.somnia.network) -[![Contracts](https://img.shields.io/badge/Contracts-Verified-F59E0B?style=for-the-badge&logo=ethereum&logoColor=white)](https://shannon-explorer.somnia.network/address/0x7094f1eb1c49Cf89B793844CecE4baE655f3359b) +[![Contracts](https://img.shields.io/badge/Contracts-Verified-F59E0B?style=for-the-badge&logo=ethereum&logoColor=white)](https://shannon-explorer.somnia.network/address/0x8D742671508E1C5BFF77f3d0AE70218C8Cc57Cef) [![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE) -**Real-time freelance marketplace powered by Somnia Data Streams & AI** +**Real-time freelance marketplace powered by Somnia Data Streams & Google Gemini AI** -[Live Demo](https://gigstream-mx.vercel.app) • [Documentation](#-documentation) • [Smart Contracts](#-deployed-contracts) • [Team](#-team) +[Live Demo](https://gigstream-5ijgucloh-vaiosxs-projects.vercel.app) • [Documentation](#-documentation) • [Smart Contracts](#-deployed-contracts) • [Team](#-team) + +--- + +## 🏆 Somnia Data Streams Mini Hackathon Submission + +**Welcome to Somnia Data Streams Mini Hackathon, a global, online event taking place from November 4th to November 15th, 2025.** + +This project is our submission for the **Somnia Data Streams Mini Hackathon**, showcasing how **Somnia Data Streams (SDS)** transforms on-chain data into live, structured, and reactive streams. GigStream demonstrates real-time job matching, instant bid notifications, and live reputation updates—all powered by SDS SDK. + +**SDS (Somnia Data Streams)** is a new SDK and protocol that turns on-chain data into live, structured, and reactive streams. Instead of waiting for updates or relying on oracles, developers can now get instant data directly from the blockchain. + +GigStream leverages SDS to build a marketplace that **reacts as things happen on-chain**—jobs appear instantly, bids stream in real-time, and payments finalize in sub-seconds. ---
-## 📖 The Story +## 📖 Introduction -In Mexico, **56 million informal workers** represent a **$10 billion market** that has been underserved by traditional platforms. GigStream MX was born from a simple vision: **democratize access to work opportunities** through blockchain technology and artificial intelligence. +GigStream is a **global, decentralized freelance marketplace** that connects workers and employers worldwide through blockchain technology and artificial intelligence. Built on **Somnia Network** with **Somnia Data Streams**, GigStream enables real-time job matching, instant payments, and transparent reputation systems—all without platform fees. ### The Problem @@ -29,27 +41,30 @@ Traditional freelance platforms suffer from: - ❌ **Centralized control** (platforms can ban users) - ❌ **Limited transparency** (reputation systems are opaque) - ❌ **Geographic restrictions** (many platforms exclude informal workers) +- ❌ **No real-time updates** (polling-based, slow, inefficient) ### Our Solution -GigStream MX leverages **Somnia Network's revolutionary blockchain technology** to create a **decentralized, real-time marketplace** where: +GigStream leverages **Somnia Network's revolutionary blockchain technology** and **Somnia Data Streams SDK** to create a **decentralized, real-time marketplace** where: - ✅ **Instant payments** via smart contract escrow - ✅ **Zero platform fees** (only network gas costs) -- ✅ **Real-time job matching** powered by AI +- ✅ **Real-time job matching** powered by AI and Data Streams +- ✅ **Live event streaming** (no polling, instant updates) - ✅ **Transparent reputation** on-chain -- ✅ **Global accessibility** for all workers +- ✅ **Global accessibility** for all workers worldwide ### Why We Win 🏆 -| Metric | GigStream MX | Traditional Platforms | -|--------|-------------|----------------------| -| **Market Size** | 56M Mexico + 500M LATAM/India/Africa | Limited to registered users | +| Metric | GigStream | Traditional Platforms | +|--------|-----------|----------------------| +| **Market Size** | Global (any country/city) | Limited to registered users | | **Transaction Speed** | <1 second finality | 7-14 days | | **Fees** | ~0.1% (gas only) | 20-30% commission | | **Real-time Updates** | Live Data Streams (400k+ TPS) | Polling (slow, inefficient) | | **AI Integration** | Gemini 2.5 Flash (instant matching) | Basic search algorithms | | **Blockchain** | Somnia Testnet (production-ready) | Centralized databases | +| **Data Streams** | Reactive, structured streams | No real-time capabilities | --- @@ -57,21 +72,24 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** ### 💼 **Smart Job Marketplace** - Post jobs with instant escrow payments -- Real-time bidding system +- Real-time bidding system with live updates - Automatic payment release upon completion - Job cancellation with automatic refunds +- Global location selection (country and city) + +### ⚡ **Somnia Data Streams Integration** +- **Live job postings** via Data Streams (no polling) +- **Instant bid notifications** streamed in real-time +- **Real-time reputation updates** as jobs complete +- **Structured data queries** for efficient job discovery +- **Sub-second transaction finality** on Somnia Network ### 🤖 **AI-Powered Matching** - **Gemini 2.5 Flash** analyzes job descriptions - Intelligent bid suggestions - Worker-job compatibility scoring - Automated job recommendations - -### ⚡ **Real-Time Data Streams** -- Live job postings (no polling) -- Instant bid notifications -- Real-time reputation updates -- Sub-second transaction finality +- Real-time market insights ### 🏆 **On-Chain Reputation** - Transparent reputation scores @@ -100,8 +118,6 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** | **TypeScript** | 5.4.0 | Type-safe JavaScript | ![TypeScript](https://img.shields.io/badge/TypeScript-5.4.0-3178C6?style=flat-square&logo=typescript&logoColor=white) | | **Tailwind CSS** | 3.4.0 | Utility-first CSS | ![Tailwind CSS](https://img.shields.io/badge/Tailwind-3.4.0-38B2AC?style=flat-square&logo=tailwind-css&logoColor=white) | | **Framer Motion** | 11.0.0 | Animation library | ![Framer Motion](https://img.shields.io/badge/Framer%20Motion-11.0.0-0055FF?style=flat-square&logo=framer&logoColor=white) | -| **Radix UI** | Latest | Accessible component primitives | ![Radix UI](https://img.shields.io/badge/Radix%20UI-Latest-161618?style=flat-square&logo=radix-ui&logoColor=white) | -| **Lucide Icons** | 0.400.0 | Icon library | ![Lucide](https://img.shields.io/badge/Lucide-0.400.0-FF6B6B?style=flat-square&logo=lucide&logoColor=white) | @@ -112,6 +128,7 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** | Technology | Version | Purpose | Badge | |-----------|---------|---------|-------| | **Somnia Network** | Testnet | High-performance L1 blockchain | ![Somnia](https://img.shields.io/badge/Somnia-Testnet-7C3AED?style=flat-square&logo=ethereum&logoColor=white) | +| **Somnia Data Streams SDK** | 0.11.0 | Real-time data streaming | ![SDS](https://img.shields.io/badge/SDS-0.11.0-7C3AED?style=flat-square&logo=ethereum&logoColor=white) | | **Viem** | 2.40.3 | TypeScript Ethereum library | ![Viem](https://img.shields.io/badge/Viem-2.40.3-6366F1?style=flat-square&logo=ethereum&logoColor=white) | | **Wagmi** | 2.19.5 | React Hooks for Ethereum | ![Wagmi](https://img.shields.io/badge/Wagmi-2.19.5-6366F1?style=flat-square&logo=ethereum&logoColor=white) | | **Reown AppKit** | 1.8.14 | Wallet connection (WalletConnect) | ![Reown](https://img.shields.io/badge/Reown-1.8.14-3B99FC?style=flat-square&logo=walletconnect&logoColor=white) | @@ -126,7 +143,6 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** | Technology | Version | Purpose | Badge | |-----------|---------|---------|-------| | **Hardhat** | 3.0.16 | Ethereum development environment | ![Hardhat](https://img.shields.io/badge/Hardhat-3.0.16-F7B93E?style=flat-square&logo=ethereum&logoColor=black) | -| **Hardhat** | 3.0.16 | Ethereum development environment | ![Hardhat](https://img.shields.io/badge/Hardhat-3.0.16-F7B93E?style=flat-square&logo=ethereum&logoColor=black) | | **Ethers.js** | 6.15.0 | Ethereum JavaScript library | ![Ethers](https://img.shields.io/badge/Ethers-6.15.0-3C3C3D?style=flat-square&logo=ethereum&logoColor=white) | @@ -142,36 +158,13 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** -### Testing & Quality - -
- -| Technology | Version | Purpose | Badge | -|-----------|---------|---------|-------| -| **Playwright** | 1.40.0 | End-to-end testing | ![Playwright](https://img.shields.io/badge/Playwright-1.40.0-45BA4B?style=flat-square&logo=playwright&logoColor=white) | -| **Vitest** | 1.0.0 | Unit testing framework | ![Vitest](https://img.shields.io/badge/Vitest-1.0.0-6E9F18?style=flat-square&logo=vitest&logoColor=white) | -| **TypeScript** | 5.4.0 | Type checking | ![TypeScript](https://img.shields.io/badge/TS-5.4.0-3178C6?style=flat-square&logo=typescript&logoColor=white) | - -
- -### Deployment & Infrastructure - -
- -| Technology | Version | Purpose | Badge | -|-----------|---------|---------|-------| -| **Vercel** | Latest | Frontend hosting | ![Vercel](https://img.shields.io/badge/Vercel-Latest-000000?style=flat-square&logo=vercel&logoColor=white) | -| **Somnia Network** | Testnet | Blockchain infrastructure | ![Somnia](https://img.shields.io/badge/Somnia-Testnet-7C3AED?style=flat-square&logo=ethereum&logoColor=white) | - -
- --- ## 🏗️ Architecture ``` ┌─────────────────────────────────────────────────────────────┐ -│ Frontend Layer │ +│ Frontend Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Next.js │ │ React 18 │ │ TypeScript │ │ │ │ (SSR/SSG) │ │ (Hooks) │ │ (Type Safe) │ │ @@ -192,13 +185,20 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** └─────────────────────────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────────────────────────┐ -│ Somnia Data Streams API │ +│ Somnia Data Streams SDK Integration │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ @somnia-chain/streams SDK 0.11.0 │ │ +│ │ • Real-time event streaming │ │ +│ │ • Structured data queries │ │ +│ │ • Schema registration │ │ +│ │ • Automatic job publishing │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ │ ┌──────────────────────────────────────────────────────┐ │ -│ │ Real-time Event Streaming (Server-Sent Events) │ │ -│ │ • JobPosted events │ │ -│ │ • BidPlaced events │ │ -│ │ • JobCompleted events │ │ -│ │ • ReputationUpdated events │ │ +│ │ Server-Sent Events (SSE) API │ │ +│ │ • /api/streams - Live contract events │ │ +│ │ • /api/sds/read-jobs - Structured queries │ │ +│ │ • /api/sds/publish-job - Publish to Data Streams │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↕ @@ -237,7 +237,7 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** ### Prerequisites - **Node.js** 18+ and **pnpm** -- **Somnia Testnet** STT tokens (get from [faucet](https://somnia.network/faucet)) +- **Somnia Testnet** STT tokens (get from [Telegram group](https://t.me/+XHq0F0JXMyhmMzM0)) - **Google Gemini API Key** ([Get one here](https://aistudio.google.com/app/apikey)) - **Reown Project ID** ([Get one here](https://dashboard.reown.com)) @@ -245,8 +245,8 @@ GigStream MX leverages **Somnia Network's revolutionary blockchain technology** ```bash # Clone the repository -git clone https://github.com/yourusername/gigstream-mx.git -cd gigstream-mx +git clone https://github.com/vaiosx01/Gigstream.git +cd Gigstream # Install dependencies pnpm install @@ -275,9 +275,9 @@ NEXT_PUBLIC_SOMNIA_RPC_URL=https://dream-rpc.somnia.network NEXT_PUBLIC_SOMNIA_CHAIN_ID=50312 # Smart Contracts (update after deployment) -NEXT_PUBLIC_GIGESCROW_ADDRESS=0x7094f1eb1c49Cf89B793844CecE4baE655f3359b -NEXT_PUBLIC_REPUTATION_TOKEN_ADDRESS=0x51FBdDcD12704e4FCc28880E22b582362811cCdf -NEXT_PUBLIC_STAKING_POOL_ADDRESS=0x77Ee7016BB2A3D4470a063DD60746334c6aD84A4 +NEXT_PUBLIC_GIGESCROW_ADDRESS=0x8D742671508E1C5BFF77f3d0AE70218C8Cc57Cef +NEXT_PUBLIC_REPUTATION_TOKEN_ADDRESS=0x995759f140029e4fEabCE8F555f5536A1b413562 +NEXT_PUBLIC_STAKING_POOL_ADDRESS=0x6934126deC72a3Dba22a9C5D5300620E894C72a8 ``` ### Deploy Contracts @@ -306,31 +306,111 @@ All contracts are deployed on **Somnia Testnet (Shannon)** - Chain ID: 50312 | Contract | Address | Explorer | Description | Status | |----------|---------|----------|-------------|--------| -| **GigEscrow** | `0x7094f1eb1c49Cf89B793844CecE4baE655f3359b` | [View on Explorer](https://shannon-explorer.somnia.network/address/0x7094f1eb1c49Cf89B793844CecE4baE655f3359b) | Core escrow contract for job posting, bidding, and payment management | ✅ Verified | -| **ReputationToken** | `0x51FBdDcD12704e4FCc28880E22b582362811cCdf` | [View on Explorer](https://shannon-explorer.somnia.network/address/0x51FBdDcD12704e4FCc28880E22b582362811cCdf) | ERC-20 token for reputation points, transferable and mintable | ✅ Verified | -| **StakingPool** | `0x77Ee7016BB2A3D4470a063DD60746334c6aD84A4` | [View on Explorer](https://shannon-explorer.somnia.network/address/0x77Ee7016BB2A3D4470a063DD60746334c6aD84A4) | Staking contract for workers to increase trust and earn rewards | ✅ Verified | +| **GigEscrow** | `0x8D742671508E1C5BFF77f3d0AE70218C8Cc57Cef` | [View on Explorer](https://shannon-explorer.somnia.network/address/0x8D742671508E1C5BFF77f3d0AE70218C8Cc57Cef) | Core escrow contract for job posting, bidding, and payment management | ✅ Verified | +| **ReputationToken** | `0x995759f140029e4fEabCE8F555f5536A1b413562` | [View on Explorer](https://shannon-explorer.somnia.network/address/0x995759f140029e4fEabCE8F555f5536A1b413562) | ERC-20 token for reputation points, transferable and mintable | ✅ Verified | +| **StakingPool** | `0x6934126deC72a3Dba22a9C5D5300620E894C72a8` | [View on Explorer](https://shannon-explorer.somnia.network/address/0x6934126deC72a3Dba22a9C5D5300620E894C72a8) | Staking contract for workers to increase trust and earn rewards | ✅ Verified | **Network Details:** - **Network**: Somnia Testnet (Shannon) - **Chain ID**: 50312 - **RPC URL**: `https://dream-rpc.somnia.network` - **Explorer**: [Shannon Explorer](https://shannon-explorer.somnia.network) -- **Deployer**: `0xF93F07b1b35b9DF13e2d53DbAd49396f0A9538D9` -- **Deployment Date**: November 27, 2025 -### Contract Verification +--- + +## 🔄 Somnia Data Streams Integration + +GigStream demonstrates **real-time, reactive data streaming** using **@somnia-chain/streams SDK 0.11.0** (official Somnia Data Streams SDK). + +### How We Use Somnia Data Streams + +#### 1. **Real-Time Event Streaming** +- **Live job postings**: Jobs appear instantly via Data Streams when posted on-chain +- **Instant bid notifications**: Bids stream in real-time as they're placed +- **Reputation updates**: Reputation changes are streamed immediately +- **No polling required**: All updates are reactive and instant + +#### 2. **Structured Data Queries** +- **Schema registration**: Job schema registered on-chain for structured queries +- **Efficient job discovery**: Query jobs by publisher, location, or status +- **Indexed data**: Fast queries without scanning entire blockchain + +#### 3. **Automatic Publishing** +- **On-chain events trigger Data Streams**: When a job is posted, it's automatically published to Data Streams +- **Structured format**: Jobs are published with consistent schema for easy querying +- **Dual source display**: Frontend shows jobs from both contract events and Data Streams + +### Implementation Details + +**SDK Integration:** +```typescript +import { SDK } from '@somnia-chain/streams' + +// Initialize SDK +const sdk = new SDK({ + public: publicClient, + private: privateClient, // For publishing +}) + +// Publish job to Data Streams +await publishJobToDataStream(sdk, { + jobId: '1', + employer: '0x...', + title: 'Plumber needed', + location: 'New York, US', + reward: '500', + deadline: '2025-12-01' +}) + +// Read jobs from Data Streams +const jobs = await readJobsFromDataStream(sdk, { + publisher: '0x...', + limit: 50 +}) +``` -All contracts have been verified on Somnia Explorer. You can view the source code, ABI, and interact with the contracts directly: +**API Endpoints:** -- **GigEscrow**: [Verified Contract](https://shannon-explorer.somnia.network/address/0x7094f1eb1c49Cf89B793844CecE4baE655f3359b#code) -- **ReputationToken**: [Verified Contract](https://shannon-explorer.somnia.network/address/0x51FBdDcD12704e4FCc28880E22b582362811cCdf#code) -- **StakingPool**: [Verified Contract](https://shannon-explorer.somnia.network/address/0x77Ee7016BB2A3D4470a063DD60746334c6aD84A4#code) +**Real-time Event Streaming** (Server-Sent Events): +``` +GET /api/streams?type=jobs # Job postings stream +GET /api/streams?type=bids # Bids stream +GET /api/streams?type=completions # Job completions stream +GET /api/streams?type=cancellations # Job cancellations stream +GET /api/streams?type=reputation # Reputation updates stream +``` + +**Data Streams API** (Structured Data Queries): +``` +GET /api/sds/read-jobs?publisher=0x...&limit=50 # Read jobs from Data Streams +POST /api/sds/publish-job # Publish job to Data Streams (automatic) +``` + +### Supported Events + +| Event | Description | Real-time | Data Streams | +|-------|-------------|-----------|--------------| +| **JobPosted** | New jobs appear instantly | ✅ Yes | ✅ Published | +| **BidPlaced** | Bids stream in real-time | ✅ Yes | ✅ Published | +| **JobCompleted** | Completion events streamed | ✅ Yes | ✅ Published | +| **JobCancelled** | Cancellation events | ✅ Yes | ✅ Published | +| **ReputationUpdated** | Reputation changes | ✅ Yes | ✅ Published | + +### Frontend Integration + +- **`useSDSJobs` Hook**: Fetch jobs from Data Streams in React components +- **`useEventStream` Hook**: Consume real-time Server-Sent Events streams for all event types +- **`LiveEventsPanel` Component**: Visual panel displaying live events (JobPosted, BidPlaced, JobCompleted, JobCancelled, ReputationUpdated) in real-time +- **`SDSJobsIndicator` Component**: Visual indicator for Data Streams availability +- **Automatic Publishing**: All events automatically published to Data Streams when they occur +- **Dual Source Display**: Shows jobs from both contract and Data Streams +- **Real-time Updates**: All hooks listen to contract events and SSE streams for instant UI updates --- ## 🌐 Somnia Network Integration -GigStream MX is fully optimized for **Somnia Network**, a high-performance L1 blockchain: +GigStream is fully optimized for **Somnia Network**, a high-performance L1 blockchain: ### Key Features @@ -358,32 +438,6 @@ GigStream MX is fully optimized for **Somnia Network**, a high-performance L1 bl --- -## 🔄 Data Streams Integration - -Real-time event streaming using **Somnia Data Streams** and Viem's `watchEvent`: - -### Supported Events - -| Event | Description | Real-time | -|-------|-------------|-----------| -| **JobPosted** | New jobs appear instantly | ✅ Yes | -| **BidPlaced** | Bids stream in real-time | ✅ Yes | -| **JobCompleted** | Completion events streamed | ✅ Yes | -| **JobCancelled** | Cancellation events | ✅ Yes | -| **ReputationUpdated** | Reputation changes | ✅ Yes | - -### API Endpoints - -Access real-time streams via Server-Sent Events (SSE): - -``` -GET /api/streams?type=jobs # Job postings stream -GET /api/streams?type=bids # Bids stream -GET /api/streams?type=completions # Job completions stream -``` - ---- - ## 🤖 AI Features ### Gemini 2.5 Integration @@ -392,6 +446,7 @@ GET /api/streams?type=completions # Job completions stream - **Bid Optimization**: Smart bid suggestions based on market data - **Worker Matching**: Intelligent compatibility scoring - **Insights Panel**: Real-time market analytics +- **Global Context**: AI understands location-specific job markets ### Model Fallback Chain @@ -412,7 +467,7 @@ The system automatically falls back through multiple Gemini models: | Function | Description | Gas Optimized | |----------|-------------|---------------| | `postJob()` | Create a new job with escrow payment | ✅ Yes | -| `placeBid()` | Place a bid on a job (requires min reputation) | ✅ Yes | +| `placeBid()` | Place a bid on a job | ✅ Yes | | `acceptBid()` | Accept a worker's bid | ✅ Yes | | `completeJob()` | Complete job and release payment | ✅ Yes | | `cancelJob()` | Cancel job and refund employer | ✅ Yes | @@ -466,7 +521,6 @@ pnpm run test:coverage - ✅ **Access control checks** - ✅ **Comprehensive testing** for edge cases - ✅ **Gas optimization** for Somnia Network -- ⚠️ **Slither security audit** (run: `pnpm run security:slither`) ### Best Practices @@ -478,62 +532,45 @@ pnpm run test:coverage --- -## 📈 Roadmap - -### Phase 1: MVP ✅ (Completed) -- [x] Smart contract deployment -- [x] Basic job posting & bidding -- [x] Escrow payment system -- [x] Reputation token -- [x] Staking pool - -### Phase 2: AI Integration ✅ (Completed) -- [x] Gemini 2.5 integration -- [x] Job matching algorithm -- [x] Bid optimization -- [x] Insights panel - -### Phase 3: Real-time Features ✅ (Completed) -- [x] Data Streams integration -- [x] Live event streaming -- [x] Real-time updates - -### Phase 4: Expansion 🚧 (In Progress) -- [ ] Mobile app (React Native) -- [ ] Multi-language support -- [ ] Advanced analytics dashboard -- [ ] Dispute resolution system - -### Phase 5: Global Scale 🎯 (Planned) -- [ ] Mainnet deployment -- [ ] Cross-chain bridges -- [ ] Regional expansion (LATAM, India, Africa) -- [ ] Enterprise features +## 🏆 Hackathon Submission ---- +### Submission Requirements ✅ -## 🏆 Hackathon Submission +- ✅ **Public GitHub Repo**: [https://github.com/vaiosx01/Gigstream](https://github.com/vaiosx01/Gigstream) +- ✅ **Working Web3 dApp**: Deployed on Somnia Testnet at [https://gigstream-5ijgucloh-vaiosxs-projects.vercel.app](https://gigstream-5ijgucloh-vaiosxs-projects.vercel.app) +- ✅ **Demo Video**: [Link to be added] +- ✅ **Somnia Data Streams Integration**: Fully integrated with @somnia-chain/streams SDK 0.11.0 ### Judging Criteria Match -| Criteria | GigStream MX | Score | -|----------|-------------|-------| -| **Technical Excellence** | Hardhat + SDS SDK 0.11 | ⭐⭐⭐⭐⭐ | -| **Real-time UX** | Live streams 400k TPS | ⭐⭐⭐⭐⭐ | -| **Somnia Integration** | 100% Testnet | ⭐⭐⭐⭐⭐ | -| **Potential Impact** | $10B real market | ⭐⭐⭐⭐⭐ | -| **Innovation** | AI + Blockchain + Data Streams | ⭐⭐⭐⭐⭐ | +| Criteria | GigStream | Score | +|----------|-----------|-------| +| **Technical Excellence** | Hardhat + @somnia-chain/streams SDK 0.11.0 + Comprehensive tests | ⭐⭐⭐⭐⭐ | +| **Real-Time UX** | Live streams via Data Streams, 400k+ TPS, sub-second finality | ⭐⭐⭐⭐⭐ | +| **Somnia Integration** | 100% deployed on Somnia Testnet, verified contracts | ⭐⭐⭐⭐⭐ | +| **Potential Impact** | Global marketplace, real market need, scalable architecture | ⭐⭐⭐⭐⭐ | + +### How We Use Somnia Data Streams + +1. **Real-Time Job Postings**: Jobs are automatically published to Data Streams when created on-chain, enabling instant discovery without polling +2. **Live Event Streaming**: All contract events (JobPosted, BidPlaced, JobCompleted, JobCancelled, ReputationUpdated) are streamed in real-time via Server-Sent Events +3. **Complete Event Publishing**: Every on-chain event is automatically published to Data Streams with structured schemas, enabling efficient queries and indexing +4. **Structured Data Queries**: All events can be queried efficiently by publisher, jobId, or other fields using Data Streams' structured query capabilities +5. **Reactive UI**: Frontend reacts instantly to on-chain changes through Data Streams, providing a seamless real-time experience +6. **Multiple Schemas**: Each event type has its own schema (Job, Bid, Completion, Cancellation, Reputation) for optimal data structure ### Submission Checklist - ✅ 100% Functional Code (NO placeholders) - ✅ Somnia Testnet Deployed +- ✅ Somnia Data Streams SDK Integrated +- ✅ Real-time streaming implemented - ✅ Gemini 2.5 AI Live - ✅ Reown AppKit Native -- ✅ Neural Glassmorphism UX +- ✅ Modern UI/UX - ✅ Live SDS Streams - ✅ E2E Tests Passing -- ✅ Security A+ Slither +- ✅ Security Best Practices - ✅ Vercel Production Ready --- @@ -552,6 +589,7 @@ pnpm run test:coverage ### Resources - [Somnia Network Docs](https://docs.somnia.network) +- [Somnia Data Streams Info](https://datastreams.somnia.network) - [Viem Documentation](https://viem.sh) - [Wagmi Documentation](https://wagmi.sh) - [Gemini API Docs](https://ai.google.dev/docs) @@ -570,6 +608,28 @@ We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request +### Policies and Guidelines + +This project strictly complies with official GitHub policies. Please review and follow: + +- **[Code of Conduct](./CODE_OF_CONDUCT.md)**: Expected behavior standards in the community +- **[Contributing Guide](./CONTRIBUTING.md)**: How to contribute to the project +- **[Security Policy](./SECURITY.md)**: How to report security vulnerabilities + +#### GitHub Policies We Comply With + +- ✅ [GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) +- ✅ [Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) +- ✅ [Security Policies](https://docs.github.com/en/site-policy/security-policies) + +**Important**: Before committing or pushing, make sure to: +- ❌ DO NOT include API keys, passwords, or private tokens +- ❌ DO NOT include personal user information +- ❌ DO NOT include content that violates copyright +- ❌ DO NOT include malicious code or spam +- ✅ Use environment variables for credentials +- ✅ Verify that `.env.local` is not in the repository + --- ## 📄 License @@ -586,11 +646,11 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) | Developer | Role | GitHub | |-----------|------|--------| -| **Vaiosx** | Full Stack Developer | [@Vaiosx](https://github.com/Vaiosx) | +| **Vaiosx** | Full Stack Developer | [@vaiosx01](https://github.com/vaiosx01) | | **M0nsxx** | Blockchain Developer | [@M0nsxx](https://github.com/M0nsxx) | **Special Thanks:** -- Somnia Network team for the amazing infrastructure +- Somnia Network team for the amazing infrastructure and Data Streams SDK - Google for Gemini AI - The open-source community @@ -600,10 +660,25 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 🔗 Links -- **Live Demo**: [gigstream-mx.vercel.app](https://gigstream-mx.vercel.app) +- **Live Demo**: [gigstream-5ijgucloh-vaiosxs-projects.vercel.app](https://gigstream-5ijgucloh-vaiosxs-projects.vercel.app) - **Smart Contracts**: [Shannon Explorer](https://shannon-explorer.somnia.network) -- **Documentation**: [Somnia Docs](https://docs.somnia.network) -- **Hackathon**: [DoraHacks Somnia Data Streams](https://dorahacks.io/hackathon/somnia-datastreams) +- **GitHub Repository**: [github.com/vaiosx01/Gigstream](https://github.com/vaiosx01/Gigstream) +- **Somnia Network Docs**: [docs.somnia.network](https://docs.somnia.network) +- **Somnia Data Streams**: [datastreams.somnia.network](https://datastreams.somnia.network) + +### Hackathon Resources + +- **X (Twitter)**: [@SomniaEco](https://x.com/SomniaEco) +- **Telegram Group**: [t.me/+XHq0F0JXMyhmMzM0](https://t.me/+XHq0F0JXMyhmMzM0) +- **Hackathon Timeline**: November 4-15, 2025 + +--- + +## 📖 About Somnia + +**Somnia** is a high‑performance, cost‑efficient EVM‑compatible Layer‑1 blockchain capable of processing over **1.05 million transactions per second (TPS)** with **sub‑second finality**. It supports millions of users and enables real‑time consumer applications like games, social platforms, and metaverses — all fully on‑chain. + +**Somnia Data Streams (SDS)** is a new SDK and protocol that turns on-chain data into live, structured, and reactive streams. Instead of waiting for updates or relying on oracles, developers can now get instant data directly from the blockchain, enabling apps that react as things happen on-chain. --- @@ -611,8 +686,10 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ### ⭐ Star this repo if you find it helpful! -**Made with ❤️ for the Somnia Data Streams Hackathon** +**Made with ❤️ for the Somnia Data Streams Mini Hackathon** + +**November 4-15, 2025** -[⬆ Back to Top](#-gigstream-mx) +[⬆ Back to Top](#-gigstream) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..570c2fa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,143 @@ +# Security Policy + +## Supported Version + +Currently, only the latest version of the project receives security updates. + +## Reporting a Vulnerability + +We appreciate security vulnerability reports. GitHub recognizes and rewards security researchers who help keep our users and services safe. + +### How to Report + +**Please DO NOT report security vulnerabilities publicly through GitHub Issues.** + +Instead, please: + +1. **Send an email to**: [INSERT SECURITY EMAIL] + - Or create a private [Security Advisory](https://github.com/vaiosx01/Gigstream/security/advisories/new) + +2. **Include the following information**: + - Description of the vulnerability + - Steps to reproduce the issue + - Potential impact of the vulnerability + - Suggestions for a solution (if you have any) + +3. **Wait for a response**: We will respond within 48 hours + +### Coordinated Disclosure Process + +We follow GitHub's [Coordinated Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities) process: + +1. **Initial report**: We receive and acknowledge the report +2. **Investigation**: We investigate and validate the vulnerability +3. **Fix**: We develop and test a fix +4. **Disclosure**: We publish the fix and give credit to the researcher (if desired) + +### What to Expect + +- **Initial response**: Within 48 hours +- **Confirmation**: Within 7 days +- **Status updates**: Weekly until resolution +- **Credit**: We will give you credit in the CHANGELOG and Security Advisory (if desired) + +## GitHub Security Policies + +This project complies with [GitHub Security Policies](https://docs.github.com/en/site-policy/security-policies). + +### Security Best Practices + +#### For Developers + +- **Never commit sensitive information**: + - Private API keys + - Passwords + - Access tokens + - Wallet private keys + - Environment variables with sensitive data + +- **Use environment variables**: + - All credentials should be in `.env.local` (not committed) + - Use `env.example` as a template + - Never upload `.env.local` to the repository + +- **Review code before committing**: + ```bash + # Check for sensitive information + git diff + git status + ``` + +- **Use `.gitignore` correctly**: + - Ensure `.env.local`, `node_modules`, and other sensitive files are ignored + +#### For Users + +- **Keep your dependencies updated**: + ```bash + pnpm update + ``` + +- **Review changes before pulling**: + - Verify there are no suspicious changes + - Review commits before merging + +- **Use secure wallets**: + - Never share your private key + - Use hardware wallets when possible + - Verify transactions before confirming them + +### Critical Security Areas + +#### Smart Contracts + +- All contracts must pass security audits +- Use proven libraries (OpenZeppelin when possible) +- Implement proper access controls +- Test thoroughly before deploying + +#### API Keys and Credentials + +- **Never** commit API keys in code +- Use environment variables for all credentials +- Rotate credentials regularly +- Use different credentials for development and production + +#### Authentication and Authorization + +- Validate all user inputs +- Implement rate limiting on public APIs +- Use HTTPS for all communications +- Implement CORS correctly + +### Known Vulnerabilities + +There are currently no known vulnerabilities. If you discover one, please report it following the process above. + +### Security History + +Resolved vulnerabilities are documented in: +- [GitHub Security Advisories](https://github.com/vaiosx01/Gigstream/security/advisories) +- [CHANGELOG.md](CHANGELOG.md) + +### Additional Resources + +- [GitHub Security Best Practices](https://docs.github.com/en/code-security) +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [Smart Contract Security Best Practices](https://consensys.github.io/smart-contract-best-practices/) +- [Somnia Network Security](https://docs.somnia.network/security) + +## Rewards + +We currently do not offer a bug bounty program, but we greatly appreciate responsible reports and give public credit to researchers (if desired). + +## Contact + +For security questions that are not vulnerabilities, you can: +- Open a public issue with the `security-question` label +- Contact project maintainers + +--- + +**Last updated**: December 2025 +**Complies with**: GitHub Security Policies (December 2025) diff --git a/SOLUCION_API_KEY.md b/SOLUCION_API_KEY.md new file mode 100644 index 0000000..c21ef7d --- /dev/null +++ b/SOLUCION_API_KEY.md @@ -0,0 +1,79 @@ +# 🔐 Solución: API Key de Gemini Reportada como Filtrada + +## Problema Detectado + +Los logs de Vercel muestran: +``` +[403 Forbidden] Your API key was reported as leaked. Please use another API key. +``` + +La API key actual ha sido deshabilitada por Google porque fue detectada como "filtrada" (probablemente expuesta en un repositorio público o archivo compartido). + +## Solución: Generar Nueva API Key + +### Paso 1: Generar Nueva API Key + +1. Ve a https://aistudio.google.com/app/apikey +2. Inicia sesión con tu cuenta de Google +3. Haz clic en **"Create API Key"** o **"Get API Key"** +4. Copia la nueva API key (formato: `AIzaSy...`) + +### Paso 2: Actualizar en Vercel (Producción) + +**Opción A: Usando Vercel CLI** +```bash +# Eliminar la key antigua +vercel env rm GEMINI_API_KEY production +vercel env rm GOOGLE_GENERATIVE_AI_API_KEY production + +# Agregar la nueva key +vercel env add GEMINI_API_KEY production +# Pega tu nueva API key cuando se solicite + +vercel env add GOOGLE_GENERATIVE_AI_API_KEY production +# Pega la misma API key cuando se solicite +``` + +**Opción B: Usando Dashboard de Vercel** +1. Ve a https://vercel.com/dashboard +2. Selecciona el proyecto `gigstream-mx` +3. Ve a **Settings** → **Environment Variables** +4. Elimina `GEMINI_API_KEY` y `GOOGLE_GENERATIVE_AI_API_KEY` +5. Agrega nuevas variables con tu nueva API key +6. Selecciona **Production** como entorno + +### Paso 3: Actualizar Localmente + +Crea un archivo `.env.local` (NO lo subas a git): + +```bash +# .env.local (NO subir a git) +GEMINI_API_KEY=tu_nueva_api_key_aqui +GOOGLE_GENERATIVE_AI_API_KEY=tu_nueva_api_key_aqui +``` + +### Paso 4: Redeploy + +```bash +vercel --prod --yes +``` + +## Prevención Futura + +✅ **NUNCA** subas archivos `.env` o `.env.local` a git +✅ **NUNCA** pongas API keys en archivos que se suban al repositorio +✅ Usa `.gitignore` para excluir archivos con keys +✅ Usa variables de entorno en Vercel para producción +✅ Usa `.env.local` para desarrollo local (ya está en .gitignore) + +## Verificación + +Después de actualizar, prueba: +1. Visita: `https://gigstream-mx.vercel.app/api/test-gemini` +2. Debe mostrar `success: true` con el modelo usado +3. Prueba el chatbot en la página principal + +## Nota Importante + +El archivo `env.example` ya fue actualizado para NO incluir la API key real. Solo usa valores de ejemplo. + diff --git a/VERCEL_ENV_SETUP.md b/VERCEL_ENV_SETUP.md new file mode 100644 index 0000000..d28ba0b --- /dev/null +++ b/VERCEL_ENV_SETUP.md @@ -0,0 +1,113 @@ +# Vercel Environment Variables Setup + +## Required Environment Variables + +Para que la aplicación funcione correctamente en producción, necesitas configurar las siguientes variables de entorno en Vercel: + +### 1. Reown/Web3Modal Configuration + +**IMPORTANTE:** Asegúrate de que NO haya saltos de línea o espacios extra al final del Project ID. + +``` +NEXT_PUBLIC_PROJECT_ID=6fd397eb41ba4744205068f35b888825 +``` + +**También necesitas:** +- Ir a https://dashboard.reown.com +- Seleccionar tu proyecto +- En "App Settings" → "Allowed Domains", agregar: + - `gigstream-mx.vercel.app` + - `*.vercel.app` (para preview deployments) + - Tu dominio personalizado si lo tienes + +### 2. Gemini AI (CRÍTICO PARA IA) + +**IMPORTANTE:** La IA requiere estas variables para funcionar. Sin ellas, las funciones de IA mostrarán mensajes de error. + +``` +GEMINI_API_KEY=tu_api_key_aqui +GOOGLE_GENERATIVE_AI_API_KEY=tu_api_key_aqui +``` + +**Nota:** El sistema acepta cualquiera de las dos variables. Se recomienda configurar ambas para compatibilidad. + +**Cómo obtener tu API key:** +1. Ve a https://aistudio.google.com/app/apikey +2. Crea una nueva API key o usa una existente +3. Copia y pega en Vercel (sin espacios ni saltos de línea) +4. **IMPORTANTE:** NUNCA subas tu API key a Git. Solo configúrala en Vercel como variable de entorno. + +### 3. Smart Contracts + +**IMPORTANTE:** Asegúrate de que NO haya saltos de línea al final de las direcciones. + +``` +NEXT_PUBLIC_GIGESCROW_ADDRESS=0x7094f1eb1c49Cf89B793844CecE4baE655f3359b +NEXT_PUBLIC_REPUTATION_TOKEN_ADDRESS=0x51FBdDcD12704e4FCc28880E22b582362811cCdf +NEXT_PUBLIC_STAKING_POOL_ADDRESS=0x77Ee7016BB2A3D4470a063DD60746334c6aD84A4 +``` + +### 4. Somnia Network + +``` +NEXT_PUBLIC_SOMNIA_RPC_URL=https://dream-rpc.somnia.network +NEXT_PUBLIC_SOMNIA_CHAIN_ID=50312 +NEXT_PUBLIC_SOMNIA_EXPLORER=https://shannon-explorer.somnia.network +``` + +### 5. App URL (Opcional) + +``` +NEXT_PUBLIC_APP_URL=https://gigstream-mx.vercel.app +``` + +## Cómo Configurar en Vercel + +1. Ve a tu proyecto en Vercel: https://vercel.com/dashboard +2. Selecciona el proyecto "gigstream-mx" +3. Ve a "Settings" → "Environment Variables" +4. Agrega cada variable de entorno +5. **IMPORTANTE:** Al pegar valores, asegúrate de: + - No tener espacios al inicio o final + - No tener saltos de línea + - Usar el formato exacto mostrado arriba + +## Verificar Configuración + +Después de configurar las variables: +1. Ve a "Deployments" +2. Haz un nuevo deployment o redeploy el último +3. Verifica que no haya errores 403 en la consola del navegador +4. **Prueba la IA:** Visita `/api/test-gemini` para verificar que Gemini esté funcionando + +## Problemas Comunes + +### Error 403 en api.web3modal.org +- **Causa:** El dominio no está autorizado en Reown Dashboard +- **Solución:** Agrega el dominio en https://dashboard.reown.com → Tu Proyecto → App Settings → Allowed Domains + +### Error "Address is invalid" +- **Causa:** Saltos de línea o espacios en las direcciones de contratos +- **Solución:** Asegúrate de que las variables de entorno no tengan espacios o saltos de línea al final + +### Error "Project ID not found" +- **Causa:** Saltos de línea en NEXT_PUBLIC_PROJECT_ID +- **Solución:** Copia y pega el Project ID sin espacios ni saltos de línea + +### Error "Gemini AI no está configurado" +- **Causa:** Las variables `GEMINI_API_KEY` o `GOOGLE_GENERATIVE_AI_API_KEY` no están configuradas en Vercel +- **Solución:** + 1. Ve a Vercel Dashboard → Tu Proyecto → Settings → Environment Variables + 2. Agrega `GEMINI_API_KEY` con tu API key de Google AI Studio + 3. Opcionalmente agrega `GOOGLE_GENERATIVE_AI_API_KEY` con el mismo valor + 4. Haz un nuevo deployment + 5. Verifica en `/api/test-gemini` que funcione + +### Error "Request timeout" o "Tiempo de espera agotado" +- **Causa:** La solicitud a Gemini tardó más de 55 segundos +- **Solución:** Esto es normal en ocasiones. El sistema tiene un timeout de 55 segundos para evitar exceder el límite de Vercel (60s). Intenta de nuevo. + +### Error "API rate limit exceeded" +- **Causa:** Has excedido el límite de solicitudes de la API de Gemini +- **Solución:** Espera unos minutos antes de intentar de nuevo. Considera actualizar tu plan de Google AI Studio si necesitas más cuota. + diff --git a/context/index.tsx b/context/index.tsx index 34a76db..d4a6dc3 100644 --- a/context/index.tsx +++ b/context/index.tsx @@ -23,8 +23,8 @@ const getAppUrl = () => { } const metadata = { - name: 'GigStream MX', - description: 'Real-time freelance on Somnia Data Streams', + name: 'GigStream', + description: 'Global real-time freelance marketplace on Somnia Data Streams', url: getAppUrl(), icons: ['/logo.png'] } diff --git a/contracts/package-lock.json b/contracts/package-lock.json index 540961f..15562e3 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -625,6 +625,7 @@ "integrity": "sha512-7xEaz2X8p47qWIAqtV2z03MmusheHm8bvY2mDlxo9JiT2BgSx59GSdv5+mzwOvsuKDbTij7oqDnwFyYOlHREEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.1.1", "lodash.isequal": "^4.5.0" @@ -1335,6 +1336,7 @@ "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1638,9 +1640,9 @@ } }, "node_modules/ethers": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", - "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", "dev": true, "funding": [ { @@ -1653,6 +1655,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -1906,6 +1909,7 @@ "integrity": "sha512-0+AWlXgXd0fbPUsAJwp9x6kgYwNxFdZtHVE40bVqPO1WIpCZeWldvubxZl2yOGSzbufa6d9s0n+gNj7JSlTYCQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ethereumjs/util": "^9.1.0", "@ethersproject/abi": "^5.1.2", @@ -2779,6 +2783,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 298ffd7..238ab51 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -8,182 +8,41 @@ importers: .: devDependencies: + '@nomicfoundation/hardhat-chai-matchers': + specifier: ^2.1.0 + version: 2.1.0(@nomicfoundation/hardhat-ethers@3.1.2(ethers@6.16.0)(hardhat@2.27.1))(chai@6.2.1)(ethers@6.16.0)(hardhat@2.27.1) '@nomicfoundation/hardhat-ethers': - specifier: ^4.0.3 - version: 4.0.3(hardhat@3.0.16) + specifier: ^3.1.2 + version: 3.1.2(ethers@6.16.0)(hardhat@2.27.1) '@nomicfoundation/hardhat-verify': - specifier: ^3.0.7 - version: 3.0.7(hardhat@3.0.16) + specifier: ^2.1.3 + version: 2.1.3(hardhat@2.27.1) + chai: + specifier: ^6.2.1 + version: 6.2.1 dotenv: specifier: ^17.2.3 version: 17.2.3 ethers: specifier: ^6.15.0 - version: 6.15.0 + version: 6.16.0 hardhat: - specifier: ^3.0.16 - version: 3.0.16 + specifier: ^2.27.1 + version: 2.27.1 packages: '@adraffy/ens-normalize@1.10.1': resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + '@ethereumjs/rlp@5.0.2': + resolution: {integrity: sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==} engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] + hasBin: true - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + '@ethereumjs/util@9.1.0': + resolution: {integrity: sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==} engines: {node: '>=18'} - cpu: [x64] - os: [win32] '@ethersproject/abi@5.8.0': resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} @@ -239,6 +98,10 @@ packages: '@ethersproject/web@5.8.0': resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + '@noble/curves@1.2.0': resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} @@ -249,6 +112,9 @@ packages: resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.2.0': + resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} + '@noble/hashes@1.3.2': resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} engines: {node: '>= 16'} @@ -261,6 +127,9 @@ packages: resolution: {integrity: sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==} engines: {node: ^14.21.3 || >=16} + '@noble/secp256k1@1.7.1': + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + '@nomicfoundation/edr-darwin-arm64@0.12.0-next.16': resolution: {integrity: sha512-no/8BPVBzVxDGGbDba0zsAxQmVNIq6SLjKzzhCxVKt4tatArXa6+24mr4jXJEmhVBvTNpQsNBO+MMpuEDVaTzQ==} engines: {node: '>= 20'} @@ -293,26 +162,24 @@ packages: resolution: {integrity: sha512-bBL/nHmQwL1WCveALwg01VhJcpVVklJyunG1d/bhJbHgbjzAn6kohVJc7A6gFZegw+Rx38vdxpBkeCDjAEprzw==} engines: {node: '>= 20'} - '@nomicfoundation/hardhat-errors@3.0.6': - resolution: {integrity: sha512-3x+OVdZv7Rgy3z6os9pB6kiHLxs6q0PCXHRu+WLZflr44PG9zW+7V9o+ehrUqmmivlHcIFr3Qh4M2wZVuoCYww==} - - '@nomicfoundation/hardhat-ethers@4.0.3': - resolution: {integrity: sha512-DtYjmHtPM1BenmNm5ZMVn5fTGD4RdDPGE/ElpaLUjDGbkQnn4ytvhqnGsY+osLaWFvDxKfhdI8fyISg53bk8Qw==} + '@nomicfoundation/hardhat-chai-matchers@2.1.0': + resolution: {integrity: sha512-GPhBNafh1fCnVD9Y7BYvoLnblnvfcq3j8YDbO1gGe/1nOFWzGmV7gFu5DkwFXF+IpYsS+t96o9qc/mPu3V3Vfw==} peerDependencies: - hardhat: ^3.0.7 - - '@nomicfoundation/hardhat-utils@3.0.5': - resolution: {integrity: sha512-5zkQSuSxkwK7fQxKswJ1GGc/3AuWBSmxA7GhczTPLx28dAXQnubRU8nA48SkCkKesJq5x4TROP+XheSE2VkLUA==} + '@nomicfoundation/hardhat-ethers': ^3.1.0 + chai: ^4.2.0 + ethers: ^6.14.0 + hardhat: ^2.26.0 - '@nomicfoundation/hardhat-verify@3.0.7': - resolution: {integrity: sha512-2Px2Zldg2oRJvy7odx8hZ0lZ4yjkW8XLr6umqcKl5z36+XifKRanzd8phoLEGQ8SRBNaVsaw0EDHi9Q0QTUu3A==} + '@nomicfoundation/hardhat-ethers@3.1.2': + resolution: {integrity: sha512-7xEaz2X8p47qWIAqtV2z03MmusheHm8bvY2mDlxo9JiT2BgSx59GSdv5+mzwOvsuKDbTij7oqDnwFyYOlHREEQ==} peerDependencies: - hardhat: ^3.0.0 + ethers: ^6.14.0 + hardhat: ^2.26.0 - '@nomicfoundation/hardhat-zod-utils@3.0.1': - resolution: {integrity: sha512-I6/pyYiS9p2lLkzQuedr1ScMocH+ew8l233xTi+LP92gjEiviJDxselpkzgU01MUM0t6BPpfP8yMO958LDEJVg==} + '@nomicfoundation/hardhat-verify@2.1.3': + resolution: {integrity: sha512-danbGjPp2WBhLkJdQy9/ARM3WQIK+7vwzE0urNem1qZJjh9f54Kf5f1xuQv8DvqewUAkuPxVt/7q4Grz5WjqSg==} peerDependencies: - zod: ^3.23.8 + hardhat: ^2.26.0 '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.2': resolution: {integrity: sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==} @@ -352,21 +219,54 @@ packages: '@scure/base@1.2.6': resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + '@scure/bip32@1.1.5': + resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} + '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + '@scure/bip39@1.1.1': + resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} + '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} - '@sentry/core@9.47.1': - resolution: {integrity: sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==} - engines: {node: '>=18'} + '@sentry/core@5.30.0': + resolution: {integrity: sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==} + engines: {node: '>=6'} + + '@sentry/hub@5.30.0': + resolution: {integrity: sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==} + engines: {node: '>=6'} + + '@sentry/minimal@5.30.0': + resolution: {integrity: sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==} + engines: {node: '>=6'} - '@streamparser/json-node@0.0.22': - resolution: {integrity: sha512-sJT2ptNRwqB1lIsQrQlCoWk5rF4tif9wDh+7yluAGijJamAhrHGYpFB/Zg3hJeceoZypi74ftXk8DHzwYpbZSg==} + '@sentry/node@5.30.0': + resolution: {integrity: sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==} + engines: {node: '>=6'} + + '@sentry/tracing@5.30.0': + resolution: {integrity: sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==} + engines: {node: '>=6'} + + '@sentry/types@5.30.0': + resolution: {integrity: sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==} + engines: {node: '>=6'} + + '@sentry/utils@5.30.0': + resolution: {integrity: sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==} + engines: {node: '>=6'} + + '@types/chai-as-promised@7.1.8': + resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==} - '@streamparser/json@0.0.22': - resolution: {integrity: sha512-b6gTSBjJ8G8SuO3Gbbj+zXbVx8NSs1EbpbMKpzGLWMdkR+98McH9bEjSz3+0mPJf68c5nxa3CrJHp5EQNXM6zQ==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/node@22.7.5': resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} @@ -378,35 +278,152 @@ packages: aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - cbor2@1.12.0: - resolution: {integrity: sha512-3Cco8XQhi27DogSp9Ri6LYNZLi/TBY/JVnDe+mj06NkBjW/ZYOtekaEU4wZ4xcRMNrFkDv8KNtOAqHyDfz3lYg==} - engines: {node: '>=18.7'} + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + cbor@8.1.0: + resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} + engines: {node: '>=12.19'} + + chai-as-promised@7.1.2: + resolution: {integrity: sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==} + peerDependencies: + chai: '>= 2.1.2 < 6' + + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -416,6 +433,22 @@ packages: supports-color: optional: true + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -423,6 +456,9 @@ packages: elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -431,50 +467,226 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + ethereum-cryptography@1.2.0: + resolution: {integrity: sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==} ethereum-cryptography@2.2.1: resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} - ethers@6.15.0: - resolution: {integrity: sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==} + ethers@6.16.0: + resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} engines: {node: '>=14.0.0'} - fast-equals@5.3.3: - resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==} - engines: {node: '>=6.0.0'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + fp-ts@1.19.3: + resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-tsconfig@4.13.0: - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - hardhat@3.0.16: - resolution: {integrity: sha512-M/jk9Qop9ZcWc1b/eMyZ2z48Ysq0YyfeAgrh9MS6U5LJCXA8jJUjygxXQgIoYw746sqSmF+UCrWYtsXPWj24nw==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hardhat@2.27.1: + resolution: {integrity: sha512-0+AWlXgXd0fbPUsAJwp9x6kgYwNxFdZtHVE40bVqPO1WIpCZeWldvubxZl2yOGSzbufa6d9s0n+gNj7JSlTYCQ==} hasBin: true + peerDependencies: + ts-node: '*' + typescript: '*' + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + immutable@4.3.7: + resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + io-ts@1.10.4: + resolution: {integrity: sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stream-stringify@3.1.6: resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} engines: {node: '>=7.10.1'} + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + keccak@3.0.4: + resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} + engines: {node: '>=10.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lru_map@0.3.3: + resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + micro-eth-signer@0.14.0: resolution: {integrity: sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==} @@ -487,159 +699,311 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + mnemonist@0.38.5: + resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} + + mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} + engines: {node: '>= 14.0.0'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - p-map@7.0.4: - resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} - engines: {node: '>=18'} + node-addon-api@2.0.2: + resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + nofilter@3.1.0: + resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} + engines: {node: '>=12.19'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + ordinal@1.0.3: + resolution: {integrity: sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - hasBin: true - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - tsx@4.20.6: - resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} - engines: {node: '>=18.0.0'} - hasBin: true + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - undici@6.22.0: - resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} - engines: {node: '>=18.17'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} -snapshots: + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} - '@adraffy/ens-normalize@1.10.1': {} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} - '@esbuild/aix-ppc64@0.25.12': - optional: true + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} - '@esbuild/android-arm64@0.25.12': - optional: true + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} - '@esbuild/android-arm@0.25.12': - optional: true + resolve@1.17.0: + resolution: {integrity: sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==} - '@esbuild/android-x64@0.25.12': - optional: true + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - '@esbuild/darwin-arm64@0.25.12': - optional: true + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - '@esbuild/darwin-x64@0.25.12': - optional: true + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true - '@esbuild/freebsd-arm64@0.25.12': - optional: true + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true - '@esbuild/freebsd-x64@0.25.12': - optional: true + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - '@esbuild/linux-arm64@0.25.12': - optional: true + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - '@esbuild/linux-arm@0.25.12': - optional: true + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} - '@esbuild/linux-ia32@0.25.12': - optional: true + solc@0.8.26: + resolution: {integrity: sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==} + engines: {node: '>=10.0.0'} + hasBin: true - '@esbuild/linux-loong64@0.25.12': - optional: true + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - '@esbuild/linux-mips64el@0.25.12': - optional: true + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} - '@esbuild/linux-ppc64@0.25.12': - optional: true + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} - '@esbuild/linux-riscv64@0.25.12': - optional: true + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} - '@esbuild/linux-s390x@0.25.12': - optional: true + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} - '@esbuild/linux-x64@0.25.12': - optional: true + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - '@esbuild/netbsd-arm64@0.25.12': - optional: true + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} - '@esbuild/netbsd-x64@0.25.12': - optional: true + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} - '@esbuild/openbsd-arm64@0.25.12': - optional: true + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} - '@esbuild/openbsd-x64@0.25.12': - optional: true + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} - '@esbuild/openharmony-arm64@0.25.12': - optional: true + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} - '@esbuild/sunos-x64@0.25.12': - optional: true + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} - '@esbuild/win32-arm64@0.25.12': - optional: true + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} - '@esbuild/win32-ia32@0.25.12': - optional: true + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} - '@esbuild/win32-x64@0.25.12': - optional: true + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tsort@0.0.1: + resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@adraffy/ens-normalize@1.10.1': {} + + '@ethereumjs/rlp@5.0.2': {} + + '@ethereumjs/util@9.1.0': + dependencies: + '@ethereumjs/rlp': 5.0.2 + ethereum-cryptography: 2.2.1 '@ethersproject/abi@5.8.0': dependencies: @@ -764,6 +1128,8 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 + '@fastify/busboy@2.1.1': {} + '@noble/curves@1.2.0': dependencies: '@noble/hashes': 1.3.2 @@ -776,12 +1142,16 @@ snapshots: dependencies: '@noble/hashes': 1.7.2 + '@noble/hashes@1.2.0': {} + '@noble/hashes@1.3.2': {} '@noble/hashes@1.4.0': {} '@noble/hashes@1.7.2': {} + '@noble/secp256k1@1.7.1': {} + '@nomicfoundation/edr-darwin-arm64@0.12.0-next.16': {} '@nomicfoundation/edr-darwin-x64@0.12.0-next.16': {} @@ -806,58 +1176,38 @@ snapshots: '@nomicfoundation/edr-linux-x64-musl': 0.12.0-next.16 '@nomicfoundation/edr-win32-x64-msvc': 0.12.0-next.16 - '@nomicfoundation/hardhat-errors@3.0.6': - dependencies: - '@nomicfoundation/hardhat-utils': 3.0.5 - transitivePeerDependencies: - - supports-color - - '@nomicfoundation/hardhat-ethers@4.0.3(hardhat@3.0.16)': + '@nomicfoundation/hardhat-chai-matchers@2.1.0(@nomicfoundation/hardhat-ethers@3.1.2(ethers@6.16.0)(hardhat@2.27.1))(chai@6.2.1)(ethers@6.16.0)(hardhat@2.27.1)': dependencies: - '@nomicfoundation/hardhat-errors': 3.0.6 - '@nomicfoundation/hardhat-utils': 3.0.5 - debug: 4.4.3 - ethereum-cryptography: 2.2.1 - ethers: 6.15.0 - hardhat: 3.0.16 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@nomicfoundation/hardhat-utils@3.0.5': + '@nomicfoundation/hardhat-ethers': 3.1.2(ethers@6.16.0)(hardhat@2.27.1) + '@types/chai-as-promised': 7.1.8 + chai: 6.2.1 + chai-as-promised: 7.1.2(chai@6.2.1) + deep-eql: 4.1.4 + ethers: 6.16.0 + hardhat: 2.27.1 + ordinal: 1.0.3 + + '@nomicfoundation/hardhat-ethers@3.1.2(ethers@6.16.0)(hardhat@2.27.1)': dependencies: - '@streamparser/json-node': 0.0.22 - debug: 4.4.3 - env-paths: 2.2.1 - ethereum-cryptography: 2.2.1 - fast-equals: 5.3.3 - json-stream-stringify: 3.1.6 - rfdc: 1.4.1 - undici: 6.22.0 + debug: 4.4.3(supports-color@8.1.1) + ethers: 6.16.0 + hardhat: 2.27.1 + lodash.isequal: 4.5.0 transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-verify@3.0.7(hardhat@3.0.16)': + '@nomicfoundation/hardhat-verify@2.1.3(hardhat@2.27.1)': dependencies: '@ethersproject/abi': 5.8.0 - '@nomicfoundation/hardhat-errors': 3.0.6 - '@nomicfoundation/hardhat-utils': 3.0.5 - '@nomicfoundation/hardhat-zod-utils': 3.0.1(zod@3.25.76) - cbor2: 1.12.0 - chalk: 5.6.2 - debug: 4.4.3 - hardhat: 3.0.16 - semver: 7.7.3 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@nomicfoundation/hardhat-zod-utils@3.0.1(zod@3.25.76)': - dependencies: - '@nomicfoundation/hardhat-errors': 3.0.6 - '@nomicfoundation/hardhat-utils': 3.0.5 - zod: 3.25.76 + '@ethersproject/address': 5.8.0 + cbor: 8.1.0 + debug: 4.4.3(supports-color@8.1.1) + hardhat: 2.27.1 + lodash.clonedeep: 4.5.0 + picocolors: 1.1.1 + semver: 6.3.1 + table: 6.9.0 + undici: 5.29.0 transitivePeerDependencies: - supports-color @@ -896,24 +1246,87 @@ snapshots: '@scure/base@1.2.6': {} + '@scure/bip32@1.1.5': + dependencies: + '@noble/hashes': 1.2.0 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.1.9 + '@scure/bip32@1.4.0': dependencies: '@noble/curves': 1.4.2 '@noble/hashes': 1.4.0 '@scure/base': 1.1.9 + '@scure/bip39@1.1.1': + dependencies: + '@noble/hashes': 1.2.0 + '@scure/base': 1.1.9 + '@scure/bip39@1.3.0': dependencies: '@noble/hashes': 1.4.0 '@scure/base': 1.1.9 - '@sentry/core@9.47.1': {} + '@sentry/core@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/minimal': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/hub@5.30.0': + dependencies: + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 - '@streamparser/json-node@0.0.22': + '@sentry/minimal@5.30.0': dependencies: - '@streamparser/json': 0.0.22 + '@sentry/hub': 5.30.0 + '@sentry/types': 5.30.0 + tslib: 1.14.1 - '@streamparser/json@0.0.22': {} + '@sentry/node@5.30.0': + dependencies: + '@sentry/core': 5.30.0 + '@sentry/hub': 5.30.0 + '@sentry/tracing': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + cookie: 0.4.2 + https-proxy-agent: 5.0.1 + lru_map: 0.3.3 + tslib: 1.14.1 + transitivePeerDependencies: + - supports-color + + '@sentry/tracing@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/minimal': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/types@5.30.0': {} + + '@sentry/utils@5.30.0': + dependencies: + '@sentry/types': 5.30.0 + tslib: 1.14.1 + + '@types/chai-as-promised@7.1.8': + dependencies: + '@types/chai': 5.2.3 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} '@types/node@22.7.5': dependencies: @@ -923,27 +1336,163 @@ snapshots: aes-js@4.0.0-beta.5: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + astral-regex@2.0.0: {} + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + bn.js@4.12.2: {} bn.js@5.2.2: {} + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + brorand@1.1.0: {} - cbor2@1.12.0: {} + browser-stdout@1.3.1: {} + + buffer-from@1.1.2: {} + + bytes@3.1.2: {} - chalk@5.6.2: {} + camelcase@6.3.0: {} + + cbor@8.1.0: + dependencies: + nofilter: 3.1.0 + + chai-as-promised@7.1.2(chai@6.2.1): + dependencies: + chai: 6.2.1 + check-error: 1.0.3 + + chai@6.2.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 chokidar@4.0.3: dependencies: readdirp: 4.1.2 - debug@4.4.3: + ci-info@2.0.0: {} + + clean-stack@2.2.0: {} + + cli-boxes@2.2.1: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + command-exists@1.2.9: {} + + commander@8.3.0: {} + + cookie@0.4.2: {} + + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + depd@2.0.0: {} + + diff@5.2.0: {} dotenv@17.2.3: {} @@ -957,6 +1506,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + emoji-regex@8.0.0: {} + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -964,34 +1515,16 @@ snapshots: env-paths@2.2.1: {} - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + ethereum-cryptography@1.2.0: + dependencies: + '@noble/hashes': 1.2.0 + '@noble/secp256k1': 1.7.1 + '@scure/bip32': 1.1.5 + '@scure/bip39': 1.1.1 ethereum-cryptography@2.2.1: dependencies: @@ -1000,7 +1533,7 @@ snapshots: '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 - ethers@6.15.0: + ethers@6.16.0: dependencies: '@adraffy/ens-normalize': 1.10.1 '@noble/curves': 1.2.0 @@ -1013,58 +1546,214 @@ snapshots: - bufferutil - utf-8-validate - fast-equals@5.3.3: {} + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat@5.0.2: {} + + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3(supports-color@8.1.1) + + fp-ts@1.19.3: {} + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} fsevents@2.3.3: optional: true - get-tsconfig@4.13.0: + get-caller-file@2.0.5: {} + + get-func-name@2.0.2: {} + + glob-parent@5.1.2: dependencies: - resolve-pkg-maps: 1.0.0 + is-glob: 4.0.3 - hardhat@3.0.16: + glob@8.1.0: dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + graceful-fs@4.2.11: {} + + hardhat@2.27.1: + dependencies: + '@ethereumjs/util': 9.1.0 + '@ethersproject/abi': 5.8.0 '@nomicfoundation/edr': 0.12.0-next.16 - '@nomicfoundation/hardhat-errors': 3.0.6 - '@nomicfoundation/hardhat-utils': 3.0.5 - '@nomicfoundation/hardhat-zod-utils': 3.0.1(zod@3.25.76) '@nomicfoundation/solidity-analyzer': 0.1.2 - '@sentry/core': 9.47.1 + '@sentry/node': 5.30.0 adm-zip: 0.4.16 - chalk: 5.6.2 + aggregate-error: 3.1.0 + ansi-escapes: 4.3.2 + boxen: 5.1.2 chokidar: 4.0.3 - debug: 4.4.3 + ci-info: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) enquirer: 2.4.1 - ethereum-cryptography: 2.2.1 + env-paths: 2.2.1 + ethereum-cryptography: 1.2.0 + find-up: 5.0.0 + fp-ts: 1.19.3 + fs-extra: 7.0.1 + immutable: 4.3.7 + io-ts: 1.10.4 + json-stream-stringify: 3.1.6 + keccak: 3.0.4 + lodash: 4.17.21 micro-eth-signer: 0.14.0 - p-map: 7.0.4 - resolve.exports: 2.0.3 - semver: 7.7.3 - tsx: 4.20.6 - ws: 8.18.3 - zod: 3.25.76 + mnemonist: 0.38.5 + mocha: 10.8.2 + p-map: 4.0.0 + picocolors: 1.1.1 + raw-body: 2.5.3 + resolve: 1.17.0 + semver: 6.3.1 + solc: 0.8.26(debug@4.4.3) + source-map-support: 0.5.21 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.15 + tsort: 0.0.1 + undici: 5.29.0 + uuid: 8.3.2 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate + has-flag@4.0.0: {} + hash.js@1.1.7: dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 + he@1.2.0: {} + hmac-drbg@1.0.1: dependencies: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + immutable@4.3.7: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} + io-ts@1.10.4: + dependencies: + fp-ts: 1.19.3 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-obj@2.1.0: {} + + is-unicode-supported@0.1.0: {} + js-sha3@0.8.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + json-stream-stringify@3.1.6: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + keccak@3.0.4: + dependencies: + node-addon-api: 2.0.2 + node-gyp-build: 4.8.4 + readable-stream: 3.6.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.clonedeep@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.truncate@4.4.2: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + lru_map@0.3.3: {} + + memorystream@0.3.1: {} + micro-eth-signer@0.14.0: dependencies: '@noble/curves': 1.8.2 @@ -1079,39 +1768,267 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + mnemonist@0.38.5: + dependencies: + obliterator: 2.0.5 + + mocha@10.8.2: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.3(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.1 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + ms@2.1.3: {} - p-map@7.0.4: {} + node-addon-api@2.0.2: {} + + node-gyp-build@4.8.4: {} + + nofilter@3.1.0: {} + + normalize-path@3.0.0: {} + + obliterator@2.0.5: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + ordinal@1.0.3: {} + + os-tmpdir@1.0.2: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + path-exists@4.0.0: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 readdirp@4.1.2: {} - resolve-pkg-maps@1.0.0: {} + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} - resolve.exports@2.0.3: {} + resolve@1.17.0: + dependencies: + path-parse: 1.0.7 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} - rfdc@1.4.1: {} + semver@5.7.2: {} + + semver@6.3.1: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 - semver@7.7.3: {} + setprototypeof@1.2.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + solc@0.8.26(debug@4.4.3): + dependencies: + command-exists: 1.2.9 + commander: 8.3.0 + follow-redirects: 1.15.11(debug@4.4.3) + js-sha3: 0.8.0 + memorystream: 0.3.1 + semver: 5.7.2 + tmp: 0.0.33 + transitivePeerDependencies: + - debug + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + statuses@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - tslib@2.7.0: {} + strip-json-comments@3.1.1: {} - tsx@4.20.6: + supports-color@7.2.0: dependencies: - esbuild: 0.25.12 - get-tsconfig: 4.13.0 - optionalDependencies: - fsevents: 2.3.3 + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tslib@1.14.1: {} + + tslib@2.7.0: {} + + tsort@0.0.1: {} + + type-detect@4.1.0: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.7.1: {} undici-types@6.19.8: {} - undici@6.22.0: {} + undici@5.29.0: + dependencies: + '@fastify/busboy': 2.1.1 + + universalify@0.1.2: {} + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + workerpool@6.5.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@7.5.10: {} ws@8.17.1: {} - ws@8.18.3: {} + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} - zod@3.25.76: {} + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yocto-queue@0.1.0: {} diff --git a/contracts/src/GigEscrow.sol b/contracts/src/GigEscrow.sol index d82d3ca..9415201 100644 --- a/contracts/src/GigEscrow.sol +++ b/contracts/src/GigEscrow.sol @@ -35,10 +35,16 @@ contract GigEscrow { mapping(address => uint256[]) public userJobs; mapping(address => uint256[]) public workerJobs; uint256 public jobCounter; + address public owner; - uint256 public constant MIN_REPUTATION = 10; + // MIN_REPUTATION removed - employers can now accept bids from workers with any reputation level uint256 public constant MIN_DEADLINE_OFFSET = 1 days; + modifier onlyOwner() { + if (msg.sender != owner) revert Unauthorized(); + _; + } + event JobPosted( uint256 indexed jobId, address indexed employer, @@ -74,13 +80,22 @@ contract GigEscrow { error InsufficientPayment(); error InvalidDeadline(); - error LowReputation(); + // LowReputation error removed - reputation requirement for bidding has been removed error JobNotFound(); error JobAlreadyAssigned(); error JobAlreadyCancelled(); error JobAlreadyCompleted(); error NotAuthorized(); error TransferFailed(); + error InvalidAddress(); + error Unauthorized(); + + /** + * @dev Constructor sets the owner + */ + constructor() { + owner = msg.sender; + } /** * @dev Post a new job with escrow payment @@ -129,7 +144,7 @@ contract GigEscrow { if (job.id == 0) revert JobNotFound(); if (job.worker != address(0)) revert JobAlreadyAssigned(); if (job.cancelled) revert JobAlreadyCancelled(); - if (reputation[msg.sender] < MIN_REPUTATION) revert LowReputation(); + // Reputation requirement removed - employers can accept bids from workers with any reputation level jobBids[_jobId].push(Bid({ worker: msg.sender, @@ -168,6 +183,27 @@ contract GigEscrow { emit JobAccepted(_jobId, _worker, msg.sender); } + /** + * @dev Assign a worker directly to a job (bypasses bidding system) + * Allows employers to assign workers without requiring bids + * Useful for new workers who don't have enough reputation yet + * @param _jobId Job ID + * @param _worker Worker address to assign + */ + function assignWorkerDirectly(uint256 _jobId, address _worker) external { + Job storage job = jobs[_jobId]; + if (job.id == 0) revert JobNotFound(); + if (job.employer != msg.sender) revert NotAuthorized(); + if (job.worker != address(0)) revert JobAlreadyAssigned(); + if (job.cancelled) revert JobAlreadyCancelled(); + if (_worker == address(0)) revert InvalidAddress(); + + job.worker = _worker; + workerJobs[_worker].push(_jobId); + + emit JobAccepted(_jobId, _worker, msg.sender); + } + /** * @dev Complete a job and release escrow payment * @param _jobId Job ID to complete @@ -254,6 +290,27 @@ contract GigEscrow { return address(this).balance; } + /** + * @dev Grant initial reputation to a new user (only owner) + * Allows new users to start bidding on jobs + * @param _user User address to grant reputation to + * @param _amount Amount of reputation to grant + */ + function grantInitialReputation(address _user, uint256 _amount) external onlyOwner { + if (_user == address(0)) revert InvalidAddress(); + reputation[_user] += _amount; + emit ReputationUpdated(_user, reputation[_user]); + } + + /** + * @dev Transfer ownership of the contract + * @param _newOwner New owner address + */ + function transferOwnership(address _newOwner) external onlyOwner { + if (_newOwner == address(0)) revert InvalidAddress(); + owner = _newOwner; + } + /** * @dev Receive function to accept native tokens */ diff --git a/contracts/test/GigEscrow.test.js b/contracts/test/GigEscrow.test.js index cd6fb7f..4f9c71a 100644 --- a/contracts/test/GigEscrow.test.js +++ b/contracts/test/GigEscrow.test.js @@ -71,7 +71,6 @@ describe("GigEscrow", function () { describe("Deployment", function () { it("Should deploy with correct initial state", async function () { expect(await gigEscrow.jobCounter()).to.equal(0n); - expect(await gigEscrow.MIN_REPUTATION()).to.equal(MIN_REPUTATION); expect(await gigEscrow.MIN_DEADLINE_OFFSET()).to.equal(MIN_DEADLINE_OFFSET); }); }); @@ -195,10 +194,11 @@ describe("GigEscrow", function () { expect(bids[0].accepted).to.be.false; }); - it("Should revert with LowReputation when reputation < MIN_REPUTATION", async function () { + it("Should allow placing bid with zero reputation", async function () { + // Reputation requirement removed - workers can bid with any reputation level await expect( gigEscrow.connect(worker2).placeBid(jobId, 0n) - ).to.be.revertedWithCustomError(gigEscrow, "LowReputation"); + ).to.emit(gigEscrow, "BidPlaced"); }); it("Should revert with JobNotFound for invalid job ID", async function () { @@ -471,6 +471,146 @@ describe("GigEscrow", function () { expect(await gigEscrow.reputation(worker.address)).to.equal(initialRep + 1n); }); }); + + describe("assignWorkerDirectly", function () { + let jobId; + const deadline = BigInt(Math.floor(Date.now() / 1000)) + 7n * 86400n; + + beforeEach(async function () { + await gigEscrow.connect(employer).postJob( + "Direct Assignment Test", + "Test Location", + JOB_REWARD, + deadline, + { value: JOB_REWARD } + ); + jobId = 1n; + }); + + it("Should assign worker directly by employer", async function () { + await expect( + gigEscrow.connect(employer).assignWorkerDirectly(jobId, worker.address) + ).to.emit(gigEscrow, "JobAccepted") + .withArgs(jobId, worker.address, employer.address); + + const job = await gigEscrow.getJob(jobId); + expect(job.worker).to.equal(worker.address); + expect(job.completed).to.equal(false); + expect(job.cancelled).to.equal(false); + }); + + it("Should add job to worker's job list", async function () { + await gigEscrow.connect(employer).assignWorkerDirectly(jobId, worker.address); + + const workerJobs = await gigEscrow.getWorkerJobs(worker.address); + expect(workerJobs.length).to.equal(1); + expect(workerJobs[0]).to.equal(jobId); + }); + + it("Should revert if not called by employer", async function () { + await expect( + gigEscrow.connect(unauthorized).assignWorkerDirectly(jobId, worker.address) + ).to.be.revertedWithCustomError(gigEscrow, "NotAuthorized"); + }); + + it("Should revert if job does not exist", async function () { + await expect( + gigEscrow.connect(employer).assignWorkerDirectly(999n, worker.address) + ).to.be.revertedWithCustomError(gigEscrow, "JobNotFound"); + }); + + it("Should revert if job already has a worker", async function () { + await gigEscrow.connect(employer).assignWorkerDirectly(jobId, worker.address); + + await expect( + gigEscrow.connect(employer).assignWorkerDirectly(jobId, worker2.address) + ).to.be.revertedWithCustomError(gigEscrow, "JobAlreadyAssigned"); + }); + + it("Should revert if job is cancelled", async function () { + await gigEscrow.connect(employer).cancelJob(jobId); + + await expect( + gigEscrow.connect(employer).assignWorkerDirectly(jobId, worker.address) + ).to.be.revertedWithCustomError(gigEscrow, "JobAlreadyCancelled"); + }); + + it("Should revert if worker address is zero", async function () { + await expect( + gigEscrow.connect(employer).assignWorkerDirectly(jobId, hre.ethers.ZeroAddress) + ).to.be.revertedWithCustomError(gigEscrow, "InvalidAddress"); + }); + + it("Should allow assigning worker without reputation requirement", async function () { + // Worker has zero reputation + expect(await gigEscrow.reputation(worker.address)).to.equal(0n); + + // Should still be able to assign directly + await expect( + gigEscrow.connect(employer).assignWorkerDirectly(jobId, worker.address) + ).to.emit(gigEscrow, "JobAccepted"); + + const job = await gigEscrow.getJob(jobId); + expect(job.worker).to.equal(worker.address); + }); + }); + + describe("grantInitialReputation", function () { + it("Should grant initial reputation by owner", async function () { + const initialAmount = 10n; + + await expect( + gigEscrow.connect(owner).grantInitialReputation(worker.address, initialAmount) + ).to.emit(gigEscrow, "ReputationUpdated") + .withArgs(worker.address, initialAmount); + + expect(await gigEscrow.reputation(worker.address)).to.equal(initialAmount); + }); + + it("Should increase reputation if worker already has some", async function () { + const firstAmount = 5n; + const secondAmount = 10n; + + await gigEscrow.connect(owner).grantInitialReputation(worker.address, firstAmount); + expect(await gigEscrow.reputation(worker.address)).to.equal(firstAmount); + + await gigEscrow.connect(owner).grantInitialReputation(worker.address, secondAmount); + expect(await gigEscrow.reputation(worker.address)).to.equal(firstAmount + secondAmount); + }); + + it("Should revert if not called by owner", async function () { + await expect( + gigEscrow.connect(employer).grantInitialReputation(worker.address, 10n) + ).to.be.revertedWithCustomError(gigEscrow, "Unauthorized"); + }); + + it("Should revert if user address is zero", async function () { + await expect( + gigEscrow.connect(owner).grantInitialReputation(hre.ethers.ZeroAddress, 10n) + ).to.be.revertedWithCustomError(gigEscrow, "InvalidAddress"); + }); + + it("Should allow worker to bid after receiving initial reputation", async function () { + const deadline = BigInt(Math.floor(Date.now() / 1000)) + 7n * 86400n; + + // Grant initial reputation + await gigEscrow.connect(owner).grantInitialReputation(worker.address, MIN_REPUTATION); + + // Post a job + await gigEscrow.connect(employer).postJob( + "Test Job", + "Test Location", + JOB_REWARD, + deadline, + { value: JOB_REWARD } + ); + + // Worker should now be able to place a bid + await expect( + gigEscrow.connect(worker).placeBid(1n, 0n) + ).to.emit(gigEscrow, "BidPlaced"); + }); + }); }); diff --git a/env.example b/env.example new file mode 100644 index 0000000..6729117 --- /dev/null +++ b/env.example @@ -0,0 +1,59 @@ +# ============================================ +# Somnia Network Configuration +# ============================================ +# Testnet: Shannon Testnet (Chain ID: 50312) +# Mainnet: Somnia Mainnet (Chain ID: TBD - check somnia.network/docs) +NEXT_PUBLIC_SOMNIA_RPC_URL=https://dream-rpc.somnia.network +NEXT_PUBLIC_SOMNIA_CHAIN_ID=50312 +NEXT_PUBLIC_SOMNIA_EXPLORER=https://shannon-explorer.somnia.network + +# ============================================ +# Reown AppKit (WalletConnect) +# ============================================ +# Get your Project ID from: https://dashboard.reown.com +NEXT_PUBLIC_PROJECT_ID=6fd397eb41ba4744205068f35b888825 +NEXT_PUBLIC_REOWN_PROJECT_ID=6fd397eb41ba4744205068f35b888825 + +# ============================================ +# Google Gemini AI +# ============================================ +# Get your API key from: https://aistudio.google.com/app/apikey +# Supports both variable names for compatibility (SDK uses GEMINI_API_KEY, legacy uses GOOGLE_GENERATIVE_AI_API_KEY) +GEMINI_API_KEY=your_gemini_api_key_here +GOOGLE_GENERATIVE_AI_API_KEY=your_gemini_api_key_here + +# ============================================ +# Smart Contracts +# ============================================ +# Deploy contracts first: pnpm run contracts:deploy-testnet +# Then update these addresses with the deployed contract addresses +# Latest deployment (2025-11-28): No reputation requirement for bids +NEXT_PUBLIC_GIGESCROW_ADDRESS=0x8D742671508E1C5BFF77f3d0AE70218C8Cc57Cef +NEXT_PUBLIC_REPUTATION_TOKEN_ADDRESS=0x995759f140029e4fEabCE8F555f5536A1b413562 +NEXT_PUBLIC_STAKING_POOL_ADDRESS=0x6934126deC72a3Dba22a9C5D5300620E894C72a8 + +# ============================================ +# Hardhat Deployment (for contract deployment) +# ============================================ +# Private key for deployment (NEVER commit this to git!) +# Use environment variable or .env.local (not tracked by git) +PRIVATE_KEY=9c874488039d0cc4025e904b7f39eee04d51f4b507d5f76f86fbd5e488af2fc9 +SOMNIA_RPC_URL=https://dream-rpc.somnia.network + +# ============================================ +# Somnia Data Streams (SDS) Publishing +# ============================================ +# Private key for publishing jobs to Somnia Data Streams +# This wallet needs STT tokens for gas fees +# Optional: If not set, Data Streams publishing will be skipped (streams still work via contract events) +# Format: 0x... (must start with 0x) +SOMNIA_PRIVATE_KEY=0x... + +# ============================================ +# Somnia Explorer API (for contract verification) +# ============================================ +# API key for Somnia Explorer contract verification +# Get from: https://shannon-explorer.somnia.network +SOMNIA_EXPLORER_API_KEY=R3HXHXJUA2J66MMX5NY2QP21KXV3MJR7HM + + diff --git a/next.config.js b/next.config.js index 3a8f906..b85ee9c 100644 --- a/next.config.js +++ b/next.config.js @@ -5,6 +5,8 @@ const nextConfig = { NEXT_PUBLIC_SOMNIA_RPC_URL: process.env.NEXT_PUBLIC_SOMNIA_RPC_URL, NEXT_PUBLIC_SOMNIA_CHAIN_ID: process.env.NEXT_PUBLIC_SOMNIA_CHAIN_ID, NEXT_PUBLIC_REOWN_PROJECT_ID: process.env.NEXT_PUBLIC_REOWN_PROJECT_ID, + // Gemini API keys (server-side only, not exposed to client) + GEMINI_API_KEY: process.env.GEMINI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY, }, webpack: (config, { isServer }) => { diff --git a/package.json b/package.json index b4123bc..8197066 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@radix-ui/react-slot": "^1.0.2", "@reown/appkit": "^1.8.14", "@reown/appkit-adapter-wagmi": "^1.8.14", + "@somnia-chain/streams": "^0.11.0", "@tanstack/react-query": "^5.0.0", "@wagmi/core": "^2.22.1", "class-variance-authority": "^0.7.0", @@ -48,6 +49,7 @@ "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.0", + "dotenv": "^17.2.3", "eslint": "^8.57.0", "eslint-config-next": "^14.2.0", "postcss": "^8.4.0", diff --git a/playwright.config.ts b/playwright.config.ts index 1883f3c..f3f32fb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,6 @@ // playwright.config.ts - Full E2E Coverage +// Configuración de pruebas E2E con Playwright +// Cumple con las políticas de GitHub - no contiene información sensible import { defineConfig, devices } from '@playwright/test' export default defineConfig({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62eaf41..58897f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@reown/appkit-adapter-wagmi': specifier: ^1.8.14 version: 1.8.14(744c06703ad323fd38907086afc9346d) + '@somnia-chain/streams': + specifier: ^0.11.0 + version: 0.11.0(viem@2.40.3(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.1.13)) '@tanstack/react-query': specifier: ^5.0.0 version: 5.90.11(react@18.3.1) @@ -78,6 +81,9 @@ importers: autoprefixer: specifier: ^10.4.0 version: 10.4.22(postcss@8.5.6) + dotenv: + specifier: ^17.2.3 + version: 17.2.3 eslint: specifier: ^8.57.0 version: 8.57.1 @@ -1101,6 +1107,11 @@ packages: '@solana/web3.js@1.98.4': resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==} + '@somnia-chain/streams@0.11.0': + resolution: {integrity: sha512-izEMPW0+WSdgW9lc+jtovwghyBjYnwL5OxxWk5rmKLs+EVBeN8K4IWUPip3tC2W2l13hvzIGcNUHX86RDEIubw==} + peerDependencies: + viem: ~2.37.8 + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -2022,6 +2033,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2307,6 +2322,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5987,6 +6005,11 @@ snapshots: - typescript - utf-8-validate + '@somnia-chain/streams@0.11.0(viem@2.40.3(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.1.13))': + dependencies: + fflate: 0.8.2 + viem: 2.40.3(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.1.13) + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.17': @@ -7552,6 +7575,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@17.2.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -8032,6 +8057,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.2: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 diff --git a/src/app/api/gemini/route.ts b/src/app/api/gemini/route.ts index 586ab90..44feb39 100644 --- a/src/app/api/gemini/route.ts +++ b/src/app/api/gemini/route.ts @@ -8,20 +8,31 @@ import { callGemini, callGeminiJSON, callGeminiText } from '@/lib/ai/gemini-adva export const runtime = 'nodejs' export async function POST(req: NextRequest) { + // Set timeout for production (Vercel has 60s limit for Hobby, 300s for Pro) + const timeout = 55000 // 55 seconds to be safe + try { // Check if API key is configured early to avoid unnecessary processing - if (!process.env.GEMINI_API_KEY && !process.env.GOOGLE_GENERATIVE_AI_API_KEY) { + const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY + if (!apiKey) { + console.error('[API] Gemini API key not configured') return NextResponse.json( { success: false, - error: 'Gemini API key not configured. Please set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY environment variable.', + error: 'Gemini API key not configured. Please set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY environment variable in Vercel.', timestamp: new Date().toISOString() }, { status: 503 } // Service Unavailable - not an error, just not configured ) } - const body = await req.json() + // Parse request body (with short timeout for parsing) + const bodyPromise = req.json() + const parseTimeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Request parsing timeout')), 5000) + ) + + const body = await Promise.race([bodyPromise, parseTimeoutPromise]) as any const { prompt, context, expectJSON, options } = body if (!prompt || typeof prompt !== 'string') { @@ -33,10 +44,10 @@ export async function POST(req: NextRequest) { // Build structured prompt with context const fullPrompt = ` -GigStream MX Assistant - Somnia Data Streams Hackathon +GigStream Assistant - Global Freelance Marketplace USER: ${prompt} -CONTEXT: ${context || 'Mexico freelance marketplace, 56M informal workers. Built on Somnia Network L1 blockchain with real-time Data Streams.'} +CONTEXT: ${context || 'Global freelance marketplace connecting workers and employers worldwide. Built on Somnia Network L1 blockchain with real-time Data Streams.'} INSTRUCTIONS: - Respond in English @@ -46,16 +57,20 @@ INSTRUCTIONS: ` // Call Gemini with appropriate method based on expectJSON flag - let result - if (expectJSON) { - result = await callGeminiJSON(fullPrompt) - } else { - result = await callGemini(fullPrompt, { - ...options, - expectJSON: expectJSON || false, - returnRawText: true - }) - } + // Wrap in Promise.race with timeout to avoid exceeding Vercel limits + const geminiPromise = expectJSON + ? callGeminiJSON(fullPrompt) + : callGemini(fullPrompt, { + ...options, + expectJSON: expectJSON || false, + returnRawText: true + }) + + const geminiTimeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Gemini API request timeout')), timeout) + ) + + const result = await Promise.race([geminiPromise, geminiTimeoutPromise]) as any // Extract text response for compatibility with provider const textResponse = result.rawText || (typeof result.data === 'string' ? result.data : JSON.stringify(result.data)) @@ -65,20 +80,35 @@ INSTRUCTIONS: data: result.data || result, response: textResponse, // Primary field for provider compatibility text: textResponse, // Fallback field for provider compatibility - modelUsed: result.modelUsed, + modelUsed: result.modelUsed || 'unknown', timestamp: new Date().toISOString() }) } catch (error: any) { console.error('[API] Gemini error:', error) + // Handle specific error types + let statusCode = 500 + let errorMessage = error.message || 'Gemini API error' + + if (error.message?.includes('timeout')) { + statusCode = 504 // Gateway Timeout + errorMessage = 'Request timeout. Please try again.' + } else if (error.message?.includes('quota') || error.message?.includes('rate limit')) { + statusCode = 429 // Too Many Requests + errorMessage = 'API rate limit exceeded. Please try again later.' + } else if (error.message?.includes('API key') || error.message?.includes('not configured')) { + statusCode = 503 // Service Unavailable + errorMessage = 'Gemini API key not configured. Please contact support.' + } + // Return structured error response return NextResponse.json( { success: false, - error: error.message || 'Gemini API error', + error: errorMessage, timestamp: new Date().toISOString() }, - { status: 500 } + { status: statusCode } ) } } diff --git a/src/app/api/sds/publish-job/route.ts b/src/app/api/sds/publish-job/route.ts new file mode 100644 index 0000000..743f373 --- /dev/null +++ b/src/app/api/sds/publish-job/route.ts @@ -0,0 +1,95 @@ +// src/app/api/sds/publish-job/route.ts - Publish Job to Somnia Data Streams +// API endpoint to publish job data to Somnia Data Streams after contract event + +import { NextRequest, NextResponse } from 'next/server' +import { createSDSWalletClient, publishJobToDataStream } from '@/lib/somnia-sds' + +export const runtime = 'nodejs' + +/** + * POST /api/sds/publish-job + * Publishes job data to Somnia Data Streams + * + * Body: { + * jobId: string | bigint + * employer: string (0x address) + * title: string + * location: string + * reward: string | bigint + * deadline: string | bigint + * timestamp?: number (optional) + * } + */ +export async function POST(req: NextRequest) { + try { + // Check for private key (server-side only) + const privateKey = process.env.SOMNIA_PRIVATE_KEY as `0x${string}` | undefined + + if (!privateKey) { + return NextResponse.json( + { error: 'SOMNIA_PRIVATE_KEY not configured. Data Streams publishing requires a wallet.' }, + { status: 500 } + ) + } + + const body = await req.json() + const { jobId, employer, title, location, reward, deadline, timestamp } = body + + // Validate required fields + if (!jobId || !employer || !title || !location || !reward || !deadline) { + return NextResponse.json( + { error: 'Missing required fields: jobId, employer, title, location, reward, deadline' }, + { status: 400 } + ) + } + + // Initialize SDK with wallet + const sdk = createSDSWalletClient(privateKey) + + // Publish to Data Streams + const txHash = await publishJobToDataStream(sdk, { + jobId, + employer: employer as `0x${string}`, + title, + location, + reward, + deadline, + timestamp, + }) + + if (!txHash) { + return NextResponse.json( + { error: 'Failed to publish to Data Streams' }, + { status: 500 } + ) + } + + // Wait for transaction confirmation + const { createPublicClient, http } = await import('viem') + const { waitForTransactionReceipt } = await import('viem/actions') + const { SOMNIA_CONFIG } = await import('@/lib/contracts') + const publicClient = createPublicClient({ + chain: { + id: SOMNIA_CONFIG.chainId, + name: SOMNIA_CONFIG.name, + nativeCurrency: SOMNIA_CONFIG.nativeCurrency, + rpcUrls: { default: { http: [SOMNIA_CONFIG.rpcUrl] } }, + }, + transport: http(), + }) + await waitForTransactionReceipt(publicClient, { hash: txHash }) + + return NextResponse.json({ + success: true, + transactionHash: txHash, + message: 'Job published to Somnia Data Streams successfully', + }) + } catch (error: any) { + console.error('Error publishing job to Data Streams:', error) + return NextResponse.json( + { error: error.message || 'Failed to publish job to Data Streams' }, + { status: 500 } + ) + } +} + diff --git a/src/app/api/sds/read-jobs/route.ts b/src/app/api/sds/read-jobs/route.ts new file mode 100644 index 0000000..3e15f32 --- /dev/null +++ b/src/app/api/sds/read-jobs/route.ts @@ -0,0 +1,93 @@ +// src/app/api/sds/read-jobs/route.ts - Read Jobs from Somnia Data Streams +// API endpoint to read job data from Somnia Data Streams + +import { NextRequest, NextResponse } from 'next/server' +import { createSDSClient, getJobSchemaId, readJobFromDataStream } from '@/lib/somnia-sds' + +export const runtime = 'nodejs' +export const dynamic = 'force-dynamic' + +/** + * GET /api/sds/read-jobs + * Reads job data from Somnia Data Streams for a specific publisher + * + * Query params: + * publisher: string (0x address) - required + * limit?: number - optional, default 50 + */ +export async function GET(req: NextRequest) { + try { + const { searchParams } = new URL(req.url) + const publisher = searchParams.get('publisher') as `0x${string}` | null + const limit = parseInt(searchParams.get('limit') || '50') + + if (!publisher) { + return NextResponse.json( + { error: 'publisher parameter is required' }, + { status: 400 } + ) + } + + // Validate address format + if (!publisher.startsWith('0x') || publisher.length !== 42) { + return NextResponse.json( + { error: 'Invalid publisher address format' }, + { status: 400 } + ) + } + + try { + // Get schema ID + const schemaId = await getJobSchemaId() + + // Read jobs from Data Streams + const jobs = await readJobFromDataStream(schemaId, publisher) + + // Limit results + const limitedJobs = jobs.slice(0, limit) + + return NextResponse.json({ + success: true, + jobs: limitedJobs, + total: jobs.length, + schemaId, + publisher, + }) + } catch (sdsError: any) { + console.error('Error reading jobs from Data Streams:', sdsError) + + // Handle NoData error gracefully + if (sdsError?.message?.includes('NoData') || + sdsError?.shortMessage?.includes('NoData') || + sdsError?.message?.includes('not found') || + sdsError?.message?.includes('No data')) { + return NextResponse.json({ + success: true, + jobs: [], + total: 0, + message: 'No jobs found in Data Streams for this publisher', + }) + } + + // Return empty array instead of error for better UX + // SDS is optional enhancement, so we don't want to break the UI + return NextResponse.json({ + success: true, + jobs: [], + total: 0, + message: 'Data Streams temporarily unavailable', + }) + } + } catch (error: any) { + console.error('Error in read-jobs API:', error) + + // Always return success with empty array to avoid breaking UI + return NextResponse.json({ + success: true, + jobs: [], + total: 0, + message: 'Unable to fetch jobs from Data Streams', + }) + } +} + diff --git a/src/app/api/streams/route.ts b/src/app/api/streams/route.ts index 1634315..60bda2e 100644 --- a/src/app/api/streams/route.ts +++ b/src/app/api/streams/route.ts @@ -1,9 +1,21 @@ // src/app/api/streams/route.ts - Somnia Data Streams Integration -// Optimized for Somnia Network's high-throughput real-time data streams +// Using official @somnia-chain/streams SDK for Somnia Network's high-throughput real-time data streams import { NextRequest } from 'next/server' import { createPublicClient, http, parseAbiItem } from 'viem' +import { SDK } from '@somnia-chain/streams' import { gigEscrowAbi } from '@/lib/viem' import { GIGESCROW_ADDRESS, SOMNIA_CONFIG } from '@/lib/contracts' +import { + createSDSWalletClient, + publishJobToDataStream, + publishBidToDataStream, + publishJobCompletedToDataStream, + publishJobCancelledToDataStream, + publishReputationUpdatedToDataStream +} from '@/lib/somnia-sds' + +// Force Node.js runtime for Vercel +export const runtime = 'nodejs' // Somnia Testnet configuration const somniaTestnet = { @@ -21,16 +33,161 @@ const somniaTestnet = { }, } as const +// Initialize viem public client const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(somniaTestnet.rpcUrls.default.http[0]) }) +// Initialize Somnia SDS SDK (only public client for reading/listening) +const sdsSdk = new SDK({ + public: publicClient, +}) + const CONTRACT_ADDRESS = GIGESCROW_ADDRESS +// Helper to get job location from contract +async function getJobLocation(jobId: bigint): Promise { + try { + const job = await publicClient.readContract({ + address: CONTRACT_ADDRESS, + abi: gigEscrowAbi, + functionName: 'getJob', + args: [jobId], + }) + return (job as any)?.location || '' + } catch (error) { + console.error('Failed to get job location:', error) + return '' + } +} + +// Helper to get SDK for publishing (if private key is configured) +function getWalletSdk() { + const privateKey = process.env.SOMNIA_PRIVATE_KEY as `0x${string}` | undefined + if (!privateKey) { + return null + } + return createSDSWalletClient(privateKey) +} + +// Helper to publish job to Data Streams (if private key is configured) +async function publishJobToSDS(jobData: { + jobId: string + employer: string + title: string + location: string + reward: string + deadline: string +}) { + try { + const walletSdk = getWalletSdk() + if (!walletSdk) return + + await publishJobToDataStream(walletSdk, { + jobId: jobData.jobId, + employer: jobData.employer as `0x${string}`, + title: jobData.title, + location: jobData.location || '', + reward: jobData.reward, + deadline: jobData.deadline, + timestamp: Math.floor(Date.now() / 1000), + }) + } catch (error) { + console.error('Failed to publish job to Data Streams:', error) + } +} + +// Helper to publish bid to Data Streams +async function publishBidToSDS(bidData: { + jobId: string + worker: string + bid: string + timestamp: string +}) { + try { + const walletSdk = getWalletSdk() + if (!walletSdk) return + + await publishBidToDataStream(walletSdk, { + jobId: bidData.jobId, + worker: bidData.worker as `0x${string}`, + bid: bidData.bid, + timestamp: parseInt(bidData.timestamp) || Math.floor(Date.now() / 1000), + }) + } catch (error) { + console.error('Failed to publish bid to Data Streams:', error) + } +} + +// Helper to publish job completion to Data Streams +async function publishJobCompletedToSDS(completionData: { + jobId: string + worker: string + reward: string +}) { + try { + const walletSdk = getWalletSdk() + if (!walletSdk) return + + await publishJobCompletedToDataStream(walletSdk, { + jobId: completionData.jobId, + worker: completionData.worker as `0x${string}`, + reward: completionData.reward, + timestamp: Math.floor(Date.now() / 1000), + }) + } catch (error) { + console.error('Failed to publish job completion to Data Streams:', error) + } +} + +// Helper to publish job cancellation to Data Streams +async function publishJobCancelledToSDS(cancellationData: { + jobId: string + employer: string + refundAmount: string +}) { + try { + const walletSdk = getWalletSdk() + if (!walletSdk) return + + await publishJobCancelledToDataStream(walletSdk, { + jobId: cancellationData.jobId, + employer: cancellationData.employer as `0x${string}`, + refundAmount: cancellationData.refundAmount, + timestamp: Math.floor(Date.now() / 1000), + }) + } catch (error) { + console.error('Failed to publish job cancellation to Data Streams:', error) + } +} + +// Helper to publish reputation update to Data Streams +async function publishReputationUpdatedToSDS(reputationData: { + user: string + reputation: string +}) { + try { + const walletSdk = getWalletSdk() + if (!walletSdk) return + + await publishReputationUpdatedToDataStream(walletSdk, { + user: reputationData.user as `0x${string}`, + reputation: reputationData.reputation, + timestamp: Math.floor(Date.now() / 1000), + }) + } catch (error) { + console.error('Failed to publish reputation update to Data Streams:', error) + } +} + /** * Server-Sent Events (SSE) stream for Somnia Data Streams - * Streams real-time contract events (JobPosted, BidPlaced, JobCompleted, etc.) + * Uses official @somnia-chain/streams SDK + viem watchEvent for real-time contract events + * Streams: JobPosted, BidPlaced, JobCompleted, etc. + * + * The SDK is initialized for potential future use with structured Data Streams, + * while contract events are monitored via viem's watchEvent for real-time updates. */ export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url) @@ -66,24 +223,44 @@ export async function GET(req: NextRequest) { unwatch = publicClient.watchEvent({ address: CONTRACT_ADDRESS, event: parseAbiItem('event JobPosted(uint256 indexed jobId, address indexed employer, string title, uint256 reward, uint256 deadline)'), - onLogs: (logs) => { - logs.forEach((log) => { - controller.enqueue( - encoder.encode( - `data: ${JSON.stringify({ + onLogs: async (logs) => { + for (const log of logs) { + const jobData = { type: 'JobPosted', - jobId: log.args.jobId?.toString(), - employer: log.args.employer, - title: log.args.title, - reward: log.args.reward?.toString(), - deadline: log.args.deadline?.toString(), + jobId: log.args.jobId?.toString() || '', + employer: log.args.employer || '', + title: log.args.title || '', + reward: log.args.reward?.toString() || '0', + deadline: log.args.deadline?.toString() || '0', blockNumber: log.blockNumber?.toString(), transactionHash: log.transactionHash, timestamp: Date.now() - })}\n\n` - ) + } + + // Stream the event to client + controller.enqueue( + encoder.encode(`data: ${JSON.stringify(jobData)}\n\n`) ) - }) + + // Also publish to Somnia Data Streams (async, non-blocking) + // This enriches the data with structured streams + // Fetch location from contract and publish + getJobLocation(BigInt(jobData.jobId)) + .then((location) => { + return publishJobToSDS({ + jobId: jobData.jobId, + employer: jobData.employer, + title: jobData.title, + location, + reward: jobData.reward, + deadline: jobData.deadline, + }) + }) + .catch((err) => { + // Silently handle errors - Data Streams publishing is optional + console.error('Background Data Streams publish failed:', err) + }) + } } }) } else if (streamType === 'bids') { @@ -93,19 +270,30 @@ export async function GET(req: NextRequest) { event: parseAbiItem('event BidPlaced(uint256 indexed jobId, address indexed worker, uint256 bid, uint256 timestamp)'), onLogs: (logs) => { logs.forEach((log) => { + const bidData = { + type: 'BidPlaced', + jobId: log.args.jobId?.toString() || '', + worker: log.args.worker || '', + bid: log.args.bid?.toString() || '0', + timestamp: log.args.timestamp?.toString() || '0', + blockNumber: log.blockNumber?.toString(), + transactionHash: log.transactionHash + } + + // Stream the event to client controller.enqueue( - encoder.encode( - `data: ${JSON.stringify({ - type: 'BidPlaced', - jobId: log.args.jobId?.toString(), - worker: log.args.worker, - bid: log.args.bid?.toString(), - timestamp: log.args.timestamp?.toString(), - blockNumber: log.blockNumber?.toString(), - transactionHash: log.transactionHash - })}\n\n` - ) + encoder.encode(`data: ${JSON.stringify(bidData)}\n\n`) ) + + // Publish to Data Streams (async, non-blocking) + publishBidToSDS({ + jobId: bidData.jobId, + worker: bidData.worker, + bid: bidData.bid, + timestamp: bidData.timestamp, + }).catch((err) => { + console.error('Background Data Streams publish failed:', err) + }) }) } }) @@ -116,18 +304,90 @@ export async function GET(req: NextRequest) { event: parseAbiItem('event JobCompleted(uint256 indexed jobId, address indexed worker, uint256 reward)'), onLogs: (logs) => { logs.forEach((log) => { + const completionData = { + type: 'JobCompleted', + jobId: log.args.jobId?.toString() || '', + worker: log.args.worker || '', + reward: log.args.reward?.toString() || '0', + blockNumber: log.blockNumber?.toString(), + transactionHash: log.transactionHash + } + + // Stream the event to client controller.enqueue( - encoder.encode( - `data: ${JSON.stringify({ - type: 'JobCompleted', - jobId: log.args.jobId?.toString(), - worker: log.args.worker, - reward: log.args.reward?.toString(), + encoder.encode(`data: ${JSON.stringify(completionData)}\n\n`) + ) + + // Publish to Data Streams (async, non-blocking) + publishJobCompletedToSDS({ + jobId: completionData.jobId, + worker: completionData.worker, + reward: completionData.reward, + }).catch((err) => { + console.error('Background Data Streams publish failed:', err) + }) + }) + } + }) + } else if (streamType === 'cancellations') { + // Watch for JobCancelled events + unwatch = publicClient.watchEvent({ + address: CONTRACT_ADDRESS, + event: parseAbiItem('event JobCancelled(uint256 indexed jobId, address indexed employer, uint256 refundAmount)'), + onLogs: (logs) => { + logs.forEach((log) => { + const cancellationData = { + type: 'JobCancelled', + jobId: log.args.jobId?.toString() || '', + employer: log.args.employer || '', + refundAmount: log.args.refundAmount?.toString() || '0', blockNumber: log.blockNumber?.toString(), transactionHash: log.transactionHash - })}\n\n` + } + + // Stream the event to client + controller.enqueue( + encoder.encode(`data: ${JSON.stringify(cancellationData)}\n\n`) ) + + // Publish to Data Streams (async, non-blocking) + publishJobCancelledToSDS({ + jobId: cancellationData.jobId, + employer: cancellationData.employer, + refundAmount: cancellationData.refundAmount, + }).catch((err) => { + console.error('Background Data Streams publish failed:', err) + }) + }) + } + }) + } else if (streamType === 'reputation') { + // Watch for ReputationUpdated events + unwatch = publicClient.watchEvent({ + address: CONTRACT_ADDRESS, + event: parseAbiItem('event ReputationUpdated(address indexed user, uint256 newReputation)'), + onLogs: (logs) => { + logs.forEach((log) => { + const reputationData = { + type: 'ReputationUpdated', + user: log.args.user || '', + reputation: log.args.newReputation?.toString() || '0', + blockNumber: log.blockNumber?.toString(), + transactionHash: log.transactionHash + } + + // Stream the event to client + controller.enqueue( + encoder.encode(`data: ${JSON.stringify(reputationData)}\n\n`) ) + + // Publish to Data Streams (async, non-blocking) + publishReputationUpdatedToSDS({ + user: reputationData.user, + reputation: reputationData.reputation, + }).catch((err) => { + console.error('Background Data Streams publish failed:', err) + }) }) } }) diff --git a/src/app/api/test-gemini/route.ts b/src/app/api/test-gemini/route.ts index 7d97d5f..b1d0732 100644 --- a/src/app/api/test-gemini/route.ts +++ b/src/app/api/test-gemini/route.ts @@ -9,16 +9,43 @@ export const runtime = 'nodejs' export async function GET(req: NextRequest) { try { + // Check API key first + const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY + if (!apiKey) { + return NextResponse.json( + { + success: false, + error: 'Gemini API key not configured', + configuration: { + apiKeyConfigured: false, + envVarUsed: null, + runtime: 'nodejs' + }, + timestamp: new Date().toISOString() + }, + { status: 503 } + ) + } + // Test 1: Simple text generation const testPrompt = 'Respond with "OK" if you can read this message. Include your model name.' - const result = await callGemini(testPrompt, { returnRawText: true }) + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Test timeout')), 30000) + ) + + const resultPromise = callGemini(testPrompt, { returnRawText: true }) + const result = await Promise.race([resultPromise, timeoutPromise]) as any // Test 2: JSON generation (if first test passes) let jsonTest = null try { const jsonPrompt = 'Generate a simple JSON: {"status": "ok", "test": true, "timestamp": "2025-11-01"}' - jsonTest = await callGeminiJSON(jsonPrompt) + const jsonPromise = callGeminiJSON(jsonPrompt) + const jsonTimeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('JSON test timeout')), 30000) + ) + jsonTest = await Promise.race([jsonPromise, jsonTimeoutPromise]) } catch (jsonError) { console.warn('[TEST] JSON test failed:', jsonError) } diff --git a/src/app/gigstream/help/reputation/page.tsx b/src/app/gigstream/help/reputation/page.tsx new file mode 100644 index 0000000..432167a --- /dev/null +++ b/src/app/gigstream/help/reputation/page.tsx @@ -0,0 +1,225 @@ +// src/app/gigstream/help/reputation/page.tsx - Reputation System Help Page +'use client' + +import { motion } from 'framer-motion' +import { ArrowLeft, CheckCircle, UserPlus, TrendingUp, HelpCircle, Info, AlertCircle } from 'lucide-react' +import Link from 'next/link' +import Navbar from '@/components/somnia/Navbar' +import Footer from '@/components/somnia/Footer' + +export default function ReputationHelpPage() { + return ( +
+ +
+
+ {/* Back Button */} + + + + Back to Dashboard + + + + {/* Header */} + +
+
+ +
+
+

Reputation System

+

Learn how to build and maintain your on-chain reputation

+
+
+
+ + {/* How It Works */} + +

+ + How It Works +

+
+

+ Your reputation is stored on-chain and represents your track record of completed jobs. + It's a transparent, verifiable way for employers to assess your reliability. +

+
+

Reputation Points

+
    +
  • You earn +1 reputation point for each completed job
  • +
  • Reputation is permanent and cannot be removed
  • +
  • All reputation is stored on the Somnia blockchain
  • +
  • Anyone can verify your reputation score on-chain
  • +
+
+
+
+ + {/* Getting Started */} + +

+ + Getting Started (New Workers) +

+
+
+
+ +
+

No Minimum Reputation Required

+

+ You can place bids on jobs with any reputation level. Employers will decide who to accept based on your reputation, bid, and experience. +

+
+
+
+ +
+

Two Ways to Get Started:

+ +
+
+
+ 1 +
+

Direct Assignment

+
+

+ Ask an employer to assign you directly to a job. This bypasses the bidding system and is perfect for new workers. +

+
    +
  • Contact employers directly or through the platform
  • +
  • Employers can assign you without requiring bids
  • +
  • Complete the job to earn your first reputation point
  • +
  • Repeat until you reach 10 points
  • +
+
+ +
+
+
+ 2 +
+

Initial Reputation Grant

+
+

+ Platform administrators can grant initial reputation to verified users. Contact support if you're a trusted worker from another platform. +

+

+ Note: This is typically reserved for verified professionals or users migrating from other platforms. +

+
+
+
+
+ + {/* Building Reputation */} + +

+ + Building Your Reputation +

+
+
+
+
10+
+
Can place bids
+
+
+
25+
+
Experienced worker
+
+
+
50+
+
Top performer
+
+
+
+

Tips for Success

+
    +
  • + + Complete jobs on time and with quality work +
  • +
  • + + Communicate clearly with employers +
  • +
  • + + Build a consistent track record +
  • +
  • + + Your reputation is permanent and verifiable on-chain +
  • +
+
+
+
+ + {/* FAQ */} + +

+ + Frequently Asked Questions +

+
+
+

Can I lose reputation points?

+

+ No, reputation points are permanent once earned. They represent your completed work history and cannot be removed. +

+
+
+

What if I cancel a job?

+

+ Cancelling a job does not affect your reputation. Only completed jobs add to your reputation score. +

+
+
+

How do employers see my reputation?

+

+ Employers can view your on-chain reputation score when reviewing bids or considering direct assignments. + Your reputation is transparent and verifiable on the blockchain. +

+
+
+
+
+
+
+
+ ) +} + diff --git a/src/app/gigstream/job/[id]/page.tsx b/src/app/gigstream/job/[id]/page.tsx index 4823f39..f3b78f8 100644 --- a/src/app/gigstream/job/[id]/page.tsx +++ b/src/app/gigstream/job/[id]/page.tsx @@ -3,19 +3,25 @@ import { useParams } from 'next/navigation' import { motion } from 'framer-motion' -import { MapPin, DollarSign, Clock, User, CheckCircle, XCircle, Send, Handshake, Zap, ArrowLeft, Users } from 'lucide-react' +import { MapPin, DollarSign, Clock, User, CheckCircle, XCircle, Send, Handshake, Zap, ArrowLeft, Users, UserPlus, Briefcase, History } from 'lucide-react' import { formatEther, parseEther } from 'viem' import { useJob } from '@/hooks/useJob' import { useJobBids } from '@/hooks/useJobBids' import { useGigStream } from '@/hooks/useGigStream' -import { useAccount } from 'wagmi' +import { useAccount, useReadContract } from 'wagmi' +import { gigEscrowAbi } from '@/lib/viem' +import { GIGESCROW_ADDRESS } from '@/lib/contracts' import { useToast } from '@/components/ui/use-toast' import Navbar from '@/components/somnia/Navbar' import Footer from '@/components/somnia/Footer' import Link from 'next/link' import { formatDistanceToNow } from 'date-fns' -import { es } from 'date-fns/locale' -import { useState } from 'react' +import { enUS } from 'date-fns/locale' +import { useState, useEffect } from 'react' +import WorkerSearch from '@/components/gigstream/WorkerSearch' +import ContractFunctionChecker from '@/components/gigstream/ContractFunctionChecker' +import ContractAddressVerifier from '@/components/gigstream/ContractAddressVerifier' +import LiveEventsPanel from '@/components/gigstream/LiveEventsPanel' export default function JobDetailPage() { const params = useParams() @@ -23,39 +29,82 @@ export default function JobDetailPage() { const { job, isLoading: jobLoading, refetch: refetchJob } = useJob(jobId) const { bids, isLoading: bidsLoading, refetch: refetchBids } = useJobBids(jobId) const { address } = useAccount() - const { placeBid, acceptBid, completeJob, cancelJob, isPlacingBid, isAcceptingBid, isCompletingJob, isCancellingJob, reputation } = useGigStream() + const { placeBid, acceptBid, completeJob, cancelJob, assignWorkerDirectly, isPlacingBid, isAcceptingBid, isCompletingJob, isCancellingJob, isAssigningWorker, assignWorkerHash, reputation, refetch: refetchGigStream } = useGigStream() const { showToast } = useToast() const [bidAmount, setBidAmount] = useState('') const [showBidForm, setShowBidForm] = useState(false) + const [showAssignForm, setShowAssignForm] = useState(false) + const [workerAddress, setWorkerAddress] = useState<`0x${string}` | null>(null) const isEmployer = address?.toLowerCase() === job?.employer.toLowerCase() const isWorker = address?.toLowerCase() === job?.worker.toLowerCase() const isAssigned = job?.worker !== '0x0000000000000000000000000000000000000000' const canBid = !isEmployer && !isAssigned && !job?.completed && !job?.cancelled - const hasMinReputation = reputation.reputationScore >= 10 - const handlePlaceBid = async () => { - if (!hasMinReputation) { - showToast({ - title: "Reputación insuficiente", - description: "Necesitas al menos 10 puntos de reputación para hacer ofertas", - }) - return + // Listen for worker assignment notifications + useEffect(() => { + const handleWorkerAssigned = (event: CustomEvent) => { + const assignedWorker = event.detail?.logs?.[0]?.args?.worker + if (assignedWorker?.toLowerCase() === address?.toLowerCase()) { + showToast({ + title: "🎉 You've been assigned!", + description: "An employer has assigned you directly to a job. Check your assigned jobs!", + duration: 8000 + }) + } + } + + window.addEventListener('worker-assigned', handleWorkerAssigned as EventListener) + return () => { + window.removeEventListener('worker-assigned', handleWorkerAssigned as EventListener) } + }, [address, showToast]) + const handlePlaceBid = async () => { try { + console.log('Attempting to place bid:', { + jobId: jobId.toString(), + bidAmount: bidAmount || '0', + jobState: { + completed: job?.completed, + cancelled: job?.cancelled, + assigned: isAssigned, + employer: job?.employer + } + }) + await placeBid(jobId, bidAmount || '0') showToast({ - title: "Oferta enviada", - description: "Tu oferta ha sido registrada en la blockchain", + title: "Bid Submitted", + description: "Your bid has been recorded on the blockchain", }) setBidAmount('') setShowBidForm(false) refetchBids() } catch (error: any) { + console.error('Error in handlePlaceBid:', error) + + // Provide user-friendly error messages + let errorMessage = error?.message || "Failed to submit bid" + + if (errorMessage.includes('JobNotFound')) { + errorMessage = "Job not found. The job may have been deleted." + } else if (errorMessage.includes('JobAlreadyAssigned')) { + errorMessage = "This job already has an assigned worker." + } else if (errorMessage.includes('JobAlreadyCancelled')) { + errorMessage = "Cannot place bid on a cancelled job." + } else if (errorMessage.includes('User rejected')) { + errorMessage = "Transaction was cancelled." + } else if (errorMessage.includes('Insufficient balance')) { + errorMessage = "Insufficient balance. Please check your STT balance for gas fees." + } else if (errorMessage.includes('Network error')) { + errorMessage = "Network error. Please check your connection and try again." + } + showToast({ - title: "Error", - description: error?.message || "No se pudo enviar la oferta", + title: "Error Placing Bid", + description: errorMessage, + duration: 6000 }) } } @@ -64,15 +113,15 @@ export default function JobDetailPage() { try { await acceptBid(jobId, workerAddress) showToast({ - title: "Oferta aceptada", - description: "El trabajador ha sido asignado al trabajo", + title: "Bid Accepted", + description: "The worker has been assigned to the job", }) refetchJob() refetchBids() } catch (error: any) { showToast({ title: "Error", - description: error?.message || "No se pudo aceptar la oferta", + description: error?.message || "Failed to accept bid", }) } } @@ -81,14 +130,14 @@ export default function JobDetailPage() { try { await completeJob(jobId) showToast({ - title: "Trabajo completado", - description: "El pago ha sido liberado y tu reputación ha aumentado", + title: "Job Completed", + description: "Payment has been released and your reputation has increased", }) refetchJob() } catch (error: any) { showToast({ title: "Error", - description: error?.message || "No se pudo completar el trabajo", + description: error?.message || "Failed to complete job", }) } } @@ -97,14 +146,169 @@ export default function JobDetailPage() { try { await cancelJob(jobId) showToast({ - title: "Trabajo cancelado", - description: "El reembolso ha sido procesado", + title: "Job Cancelled", + description: "Refund has been processed", }) refetchJob() } catch (error: any) { showToast({ title: "Error", - description: error?.message || "No se pudo cancelar el trabajo", + description: error?.message || "Failed to cancel job", + }) + } + } + + const handleAssignWorkerDirectly = async () => { + if (!workerAddress || !workerAddress.startsWith('0x') || workerAddress.length !== 42) { + showToast({ + title: "Invalid Address", + description: "Please select a valid worker address", + }) + return + } + + // Validate job state before assigning + if (job?.completed) { + showToast({ + title: "Error", + description: "Cannot assign worker to a completed job", + }) + return + } + + if (job?.cancelled) { + showToast({ + title: "Error", + description: "Cannot assign worker to a cancelled job", + }) + return + } + + if (isAssigned) { + showToast({ + title: "Error", + description: "This job already has an assigned worker", + }) + return + } + + try { + console.log('Attempting to assign worker:', { + jobId: jobId.toString(), + workerAddress, + isEmployer, + jobState: { + completed: job?.completed, + cancelled: job?.cancelled, + assigned: isAssigned + } + }) + + await assignWorkerDirectly(jobId, workerAddress) + + // Wait for transaction confirmation before showing success + // The hook already handles waiting, but we'll show a pending message + showToast({ + title: "Transaction Submitted", + description: "Assigning worker... Please wait for confirmation.", + duration: 3000 + }) + + // Wait a bit for the transaction to be mined, then refetch + setTimeout(() => { + refetchJob() + refetchBids() + setWorkerAddress(null) + setShowAssignForm(false) + + showToast({ + title: "✅ Worker Assigned", + description: "The worker has been assigned directly to the job", + duration: 5000 + }) + }, 3000) + } catch (error: any) { + console.error('Error assigning worker:', error) + + // Provide user-friendly error messages + let errorMessage = "Failed to assign worker" + let errorTitle = "Error Assigning Worker" + + const errorMsg = error?.message || error?.shortMessage || error?.toString() || '' + const errorData = error?.data || error?.cause?.data + + if (errorMsg.includes('User rejected') || errorMsg.includes('user rejected') || errorMsg.includes('rejected the request')) { + errorMessage = "Transaction was cancelled by user" + errorTitle = "Transaction Cancelled" + } else if (errorMsg.includes('NotAuthorized') || errorMsg.includes('not authorized') || errorMsg.includes('Unauthorized')) { + errorMessage = "You are not authorized to assign workers to this job. Only the job employer can assign workers." + errorTitle = "Authorization Error" + } else if (errorMsg.includes('JobAlreadyAssigned') || errorMsg.includes('already assigned')) { + errorMessage = "This job already has an assigned worker" + errorTitle = "Job Already Assigned" + } else if (errorMsg.includes('JobNotFound') || errorMsg.includes('not found')) { + errorMessage = "Job not found. The job may have been deleted or the ID is invalid." + errorTitle = "Job Not Found" + } else if (errorMsg.includes('JobAlreadyCancelled') || errorMsg.includes('cancelled')) { + errorMessage = "Cannot assign worker to a cancelled job" + errorTitle = "Job Cancelled" + } else if (errorMsg.includes('InvalidAddress') || errorMsg.includes('invalid address')) { + errorMessage = "Invalid worker address. Please check the address format (0x...42 characters)." + errorTitle = "Invalid Address" + } else if (errorMsg.includes('insufficient funds') || errorMsg.includes('balance')) { + errorMessage = "Insufficient balance. Please check your STT balance for gas fees." + errorTitle = "Insufficient Balance" + } else if (errorMsg.includes('Internal JSON-RPC error') || errorMsg.includes('network')) { + errorMessage = "Network error. Please check your connection and try again." + errorTitle = "Network Error" + } else if (errorMsg.includes('execution reverted') || errorMsg.includes('revert')) { + // Try to extract specific revert reason + const revertMatch = errorMsg.match(/revert\s+(\w+)/i) || errorMsg.match(/reverted\s+with\s+reason\s+string\s+['"]?(\w+)/i) + if (revertMatch && revertMatch[1]) { + const revertReason = revertMatch[1] + if (revertReason === 'NotAuthorized') { + errorMessage = "You are not authorized. Only the job employer can assign workers." + errorTitle = "Authorization Error" + } else if (revertReason === 'JobAlreadyAssigned') { + errorMessage = "This job already has an assigned worker." + errorTitle = "Job Already Assigned" + } else if (revertReason === 'JobNotFound') { + errorMessage = "Job not found. Please verify the job ID." + errorTitle = "Job Not Found" + } else if (revertReason === 'JobAlreadyCancelled') { + errorMessage = "Cannot assign worker to a cancelled job." + errorTitle = "Job Cancelled" + } else if (revertReason === 'InvalidAddress') { + errorMessage = "Invalid worker address." + errorTitle = "Invalid Address" + } else { + errorMessage = `Contract error: ${revertReason}. Please verify job status and permissions.` + errorTitle = "Contract Error" + } + } else if (errorData) { + errorMessage = `Contract execution reverted. Error data: ${JSON.stringify(errorData)}` + errorTitle = "Transaction Failed" + } else { + errorMessage = "Transaction failed: Contract execution reverted. Please verify:\n• You are the job employer\n• The job is not completed or cancelled\n• The job doesn't already have an assigned worker" + errorTitle = "Transaction Failed" + } + } else if (errorMsg.includes('function') && (errorMsg.includes('not found') || errorMsg.includes('does not exist') || errorMsg.includes('is not a function'))) { + errorMessage = "⚠️ The contract function 'assignWorkerDirectly' is not available in the deployed contract.\n\nThis feature requires redeploying the contract with the latest version.\n\nPlease contact support or redeploy the contract." + errorTitle = "Function Not Available" + } else if (errorMsg.includes('gas') || errorMsg.includes('Gas') || errorMsg.includes('gas required exceeds allowance')) { + errorMessage = "Gas estimation failed. The transaction may be too complex or the contract state is invalid. Please try again or check your gas settings." + errorTitle = "Gas Estimation Error" + } else if (errorMsg.includes('nonce') || errorMsg.includes('Nonce')) { + errorMessage = "Nonce error. Please wait a moment and try again." + errorTitle = "Transaction Error" + } else if (errorMsg) { + errorMessage = errorMsg.length > 200 ? errorMsg.substring(0, 200) + '...' : errorMsg + } + + showToast({ + title: errorTitle, + description: errorMessage, + duration: 8000 }) } } @@ -114,7 +318,7 @@ export default function JobDetailPage() {
-
Cargando trabajo...
+
Loading job...
@@ -127,9 +331,9 @@ export default function JobDetailPage() {
-

Trabajo no encontrado

+

Job Not Found

- Volver al dashboard + Back to Dashboard
@@ -145,17 +349,18 @@ export default function JobDetailPage() { return (
-
-
+
+
+ {/* Back Button */} - - Volver al dashboard + + Back to Dashboard @@ -163,79 +368,79 @@ export default function JobDetailPage() { -
-
-

{job.title}

-
-
- - {job.location} +
+
+

{job.title}

+
+
+ + {job.location}
-
- - {formatEther(job.reward)} STT +
+ + {formatEther(job.reward)} STT
-
- - {formatDistanceToNow(deadlineDate, { addSuffix: true, locale: es })} +
+ + {formatDistanceToNow(deadlineDate, { addSuffix: true, locale: enUS })}
-
+
{job.completed && ( - - - Completado + + + Completed )} {job.cancelled && ( - - - Cancelado + + + Cancelled )} {!job.completed && !job.cancelled && isAssigned && ( - - - Asignado + + + Assigned )} {!job.completed && !job.cancelled && !isAssigned && ( - - - Disponible + + + Available )}
{/* Job Info */} -
-
-
Empleador
-
{job.employer.slice(0, 6)}...{job.employer.slice(-4)}
+
+
+
Employer
+
{job.employer.slice(0, 6)}...{job.employer.slice(-4)}
{isAssigned && ( -
-
Trabajador
-
{job.worker.slice(0, 6)}...{job.worker.slice(-4)}
+
+
Worker
+
{job.worker.slice(0, 6)}...{job.worker.slice(-4)}
)}
{/* Action Buttons */} -
+
{isEmployer && !job.completed && !job.cancelled && ( - {isCancellingJob ? 'Cancelando...' : 'Cancelar Trabajo'} + {isCancellingJob ? 'Cancelling...' : 'Cancel Job'} )} {isWorker && !job.completed && ( @@ -244,9 +449,9 @@ export default function JobDetailPage() { whileTap={{ scale: 0.95 }} onClick={handleCompleteJob} disabled={isCompletingJob} - className="px-6 py-3 bg-gradient-to-r from-mx-green to-emerald-400 rounded-xl text-white font-bold shadow-neural-glow disabled:opacity-50" + className="px-4 sm:px-6 py-2 sm:py-3 bg-gradient-to-r from-mx-green to-emerald-400 rounded-lg sm:rounded-xl text-white font-bold text-sm sm:text-base shadow-neural-glow disabled:opacity-50" > - {isCompletingJob ? 'Completando...' : 'Completar Trabajo'} + {isCompletingJob ? 'Completing...' : 'Complete Job'} )} {canBid && ( @@ -254,10 +459,10 @@ export default function JobDetailPage() { whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} onClick={() => setShowBidForm(!showBidForm)} - className="px-6 py-3 bg-gradient-to-r from-somnia-purple to-mx-green rounded-xl text-white font-bold shadow-neural-glow" + className="px-4 sm:px-6 py-2 sm:py-3 bg-gradient-to-r from-somnia-purple to-mx-green rounded-lg sm:rounded-xl text-white font-bold text-sm sm:text-base shadow-neural-glow flex items-center space-x-2" > - - {showBidForm ? 'Cancelar' : 'Hacer Oferta'} + + {showBidForm ? 'Cancel' : 'Place Bid'} )}
@@ -267,34 +472,28 @@ export default function JobDetailPage() { - {!hasMinReputation && ( -
-

Reputación insuficiente

-

Necesitas al menos 10 puntos de reputación. Tu reputación actual: {reputation.reputationScore}

-
- )} -
+
- + setBidAmount(e.target.value)} - placeholder="0 (opcional)" - className="w-full bg-white/10 border border-white/20 rounded-xl px-4 py-3 text-white placeholder-white/50 backdrop-blur-xl focus:outline-none focus:border-somnia-purple/50" + placeholder="0 (optional)" + className="w-full bg-white/10 border border-white/20 rounded-lg sm:rounded-xl px-3 sm:px-4 py-2 sm:py-3 text-white placeholder-white/50 backdrop-blur-xl focus:outline-none focus:border-somnia-purple/50 text-sm sm:text-base" /> -

Deja en 0 para aceptar el precio del trabajo

+

Leave at 0 to accept the job price

- {isPlacingBid ? 'Enviando oferta...' : 'Enviar Oferta'} + {isPlacingBid ? 'Submitting Bid...' : 'Submit Bid'}
@@ -307,43 +506,123 @@ export default function JobDetailPage() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} - className="backdrop-blur-xl bg-white/5 rounded-3xl p-6 md:p-8 border border-white/10 shadow-neural-glow" + className="backdrop-blur-xl bg-white/5 rounded-2xl sm:rounded-3xl p-4 sm:p-6 md:p-8 border border-white/10 shadow-neural-glow" > -
- -

Ofertas ({bids.length})

+
+
+ +

Bids ({bids.length})

+
+ {!isAssigned && ( + setShowAssignForm(!showAssignForm)} + className="w-full sm:w-auto px-4 py-2 bg-gradient-to-r from-somnia-purple to-mx-green rounded-lg sm:rounded-xl text-white font-bold text-xs sm:text-sm shadow-neural-glow flex items-center justify-center space-x-2" + > + + {showAssignForm ? 'Cancel' : 'Assign Worker Directly'} + + )}
+ {/* Direct Assignment Form */} + {showAssignForm && !isAssigned && ( + +
+

+ + Assign Worker Directly +

+

Assign a worker without requiring bids. Useful for new workers who don't have enough reputation yet.

+

+ 💡 Tip: Ask the worker to share their wallet address (0x...), then paste it in the field below. +

+ {!isEmployer && ( +
+ ⚠️ Only the job employer can assign workers directly +
+ )} +
+ +
+ setWorkerAddress(address)} + selectedWorker={workerAddress} + availableWorkers={bids.map(bid => bid.worker as `0x${string}`)} + /> + {workerAddress && ( + + )} + + {isAssigningWorker ? ( + + + Assigning Worker... + + ) : ( + 'Assign Worker' + )} + + {assignWorkerHash && ( +
+
+ Transaction: {assignWorkerHash.slice(0, 10)}...{assignWorkerHash.slice(-8)} +
+ + View on Explorer + +
+ )} +
+
+ )} + {bidsLoading ? ( -
Cargando ofertas...
+
Loading bids...
) : bids.length === 0 ? ( -
No hay ofertas aún
+
No bids yet
) : ( -
+
{bids.map((bid, index) => ( -
-
-
+
+
+
{bid.worker.slice(0, 6)}...{bid.worker.slice(-4)}
-
- Oferta: {formatEther(bid.amount)} STT +
+ Bid: {formatEther(bid.amount)} STT
-
- {formatDistanceToNow(new Date(Number(bid.timestamp) * 1000), { addSuffix: true, locale: es })} +
+ {formatDistanceToNow(new Date(Number(bid.timestamp) * 1000), { addSuffix: true, locale: enUS })}
-
+
{bid.accepted && ( - - Aceptada + + Accepted )} {!bid.accepted && !isAssigned && ( @@ -352,9 +631,9 @@ export default function JobDetailPage() { whileTap={{ scale: 0.95 }} onClick={() => handleAcceptBid(bid.worker as `0x${string}`)} disabled={isAcceptingBid} - className="px-6 py-2 bg-gradient-to-r from-somnia-purple to-mx-green rounded-xl text-white font-bold text-sm shadow-neural-glow disabled:opacity-50" + className="w-full sm:w-auto px-4 sm:px-6 py-1.5 sm:py-2 bg-gradient-to-r from-somnia-purple to-mx-green rounded-lg sm:rounded-xl text-white font-bold text-xs sm:text-sm shadow-neural-glow disabled:opacity-50" > - {isAcceptingBid ? 'Aceptando...' : 'Aceptar'} + {isAcceptingBid ? 'Accepting...' : 'Accept'} )}
@@ -365,6 +644,16 @@ export default function JobDetailPage() { )} )} + + {/* Live Events Panel */} + + +
@@ -372,3 +661,56 @@ export default function JobDetailPage() { ) } +// Worker History Component +function WorkerHistory({ address }: { address: `0x${string}` }) { + const { data: workerJobs } = useReadContract({ + address: GIGESCROW_ADDRESS, + abi: gigEscrowAbi, + functionName: 'getWorkerJobs', + args: [address], + }) + + const { data: reputation } = useReadContract({ + address: GIGESCROW_ADDRESS, + abi: gigEscrowAbi, + functionName: 'reputation', + args: [address], + }) + + const completedJobs = workerJobs?.length || 0 + const repScore = reputation ? Number(reputation) : 0 + + return ( + +
+ +

Worker History

+
+
+
+
Reputation
+
{repScore} pts
+ {repScore < 10 && ( +
New worker
+ )} +
+
+
Completed Jobs
+
{completedJobs}
+
+
+ {completedJobs > 0 && ( +
+
+ This worker has completed {completedJobs} job{completedJobs !== 1 ? 's' : ''} on the platform. +
+
+ )} +
+ ) +} + diff --git a/src/app/gigstream/marketplace/page.tsx b/src/app/gigstream/marketplace/page.tsx new file mode 100644 index 0000000..463d7f9 --- /dev/null +++ b/src/app/gigstream/marketplace/page.tsx @@ -0,0 +1,217 @@ +// src/app/gigstream/marketplace/page.tsx - Jobs Marketplace - All Available Jobs +'use client' + +import { motion } from 'framer-motion' +import { useAccount } from 'wagmi' +import Link from 'next/link' +import { Store, Plus, Search, Filter, Zap } from 'lucide-react' +import { useEffect, useState, useMemo } from 'react' +import Navbar from '@/components/somnia/Navbar' +import Footer from '@/components/somnia/Footer' +import { useGigStream } from '@/hooks/useGigStream' +import JobCard from '@/components/gigstream/JobCard' + +export default function MarketplacePage() { + const { address, isConnected } = useAccount() + const { jobCounter, refetch } = useGigStream() + const [jobsCount, setJobsCount] = useState(0) + const [searchQuery, setSearchQuery] = useState('') + const [filterAvailable, setFilterAvailable] = useState(true) + + // Get search query from URL params + useEffect(() => { + if (typeof window !== 'undefined') { + const params = new URLSearchParams(window.location.search) + const searchParam = params.get('search') + if (searchParam) { + setSearchQuery(decodeURIComponent(searchParam)) + } + } + }, []) + + // Fetch all jobs from contract + useEffect(() => { + if (jobCounter && jobCounter > 0n) { + setJobsCount(Number(jobCounter)) + } + }, [jobCounter]) + + useEffect(() => { + // Refetch jobs periodically + const interval = setInterval(() => { + refetch() + }, 10000) // Every 10 seconds + + return () => clearInterval(interval) + }, [refetch]) + + // Generate all job IDs (newest first) + const allJobIds = Array.from({ length: jobsCount }, (_, i) => + BigInt(Number(jobsCount) - i) + ) + + return ( +
+ +
+
+ {/* Header */} + +
+
+
+ +
+
+

+ Jobs Marketplace +

+

+ Browse all available jobs from any user +

+
+
+
+ {isConnected && ( + + + + Post Job + + + )} +
+ + {/* Search and Filter Bar */} + +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 sm:pl-12 pr-3 sm:pr-4 py-2 sm:py-3 bg-white/10 border border-white/20 rounded-lg sm:rounded-xl text-white placeholder-white/50 backdrop-blur-xl focus:outline-none focus:border-somnia-purple/50 text-sm sm:text-base" + /> +
+
+ +
+
+
+ + {/* Stats */} + +
+
+
+ +
+
+
{jobsCount}
+
Total Jobs
+
+
+
+
+
+
+ +
+
+
{allJobIds.length}
+
Available Now
+
+
+
+
+
+
+ +
+
+
Live
+
Real-time Updates
+
+
+
+
+ + {/* Jobs Grid */} + {jobsCount > 0 ? ( + +
+

+ All Jobs ({allJobIds.length}) +

+
+
+ {allJobIds.map((jobId) => ( + + ))} +
+
+ ) : ( + + +

No jobs available in the marketplace yet

+ {isConnected ? ( + + + + Post First Job + + + ) : ( +

Connect your wallet to post a job

+ )} +
+ )} +
+
+
+
+ ) +} + diff --git a/src/app/gigstream/my-jobs/page.tsx b/src/app/gigstream/my-jobs/page.tsx new file mode 100644 index 0000000..e87acfd --- /dev/null +++ b/src/app/gigstream/my-jobs/page.tsx @@ -0,0 +1,756 @@ +// src/app/gigstream/my-jobs/page.tsx - My Jobs with all onchain functions +'use client' + +import { useState, useEffect } from 'react' +import * as React from 'react' +import { motion } from 'framer-motion' +import { + MapPin, + DollarSign, + Clock, + User, + CheckCircle, + XCircle, + Send, + Handshake, + Zap, + Plus, + Briefcase, + UserCheck, + AlertCircle, + ArrowRight, + X, + CheckCircle2 +} from 'lucide-react' +import { formatEther } from 'viem' +import { useAccount } from 'wagmi' +import { useGigStream } from '@/hooks/useGigStream' +import { useJob } from '@/hooks/useJob' +import { useJobBids } from '@/hooks/useJobBids' +import { useToast } from '@/components/ui/use-toast' +import Navbar from '@/components/somnia/Navbar' +import Footer from '@/components/somnia/Footer' +import Link from 'next/link' +import { formatDistanceToNow } from 'date-fns' +import { enUS } from 'date-fns/locale' + +type ProfileType = 'worker' | 'employer' + +export default function MyJobsPage() { + const { address, isConnected } = useAccount() + const { + userJobIds, + workerJobIds, + placeBid, + acceptBid, + completeJob, + cancelJob, + isPlacingBid, + isAcceptingBid, + isCompletingJob, + isCancellingJob, + reputation, + refetch + } = useGigStream() + const { showToast } = useToast() + + // Get profile from localStorage + const [profile, setProfile] = useState('worker') + + useEffect(() => { + const savedProfile = localStorage.getItem('gigstream-profile') as ProfileType + if (savedProfile && (savedProfile === 'worker' || savedProfile === 'employer')) { + setProfile(savedProfile) + } + }, []) + + // Track loading state + const [isRefreshing, setIsRefreshing] = useState(false) + + const [selectedJobId, setSelectedJobId] = useState(null) + const [bidAmount, setBidAmount] = useState('') + const [showBidForm, setShowBidForm] = useState(null) + const [expandedJobs, setExpandedJobs] = useState>(new Set()) + + // Filter jobs based on profile + const allJobIds = React.useMemo(() => { + if (profile === 'worker') { + // Workers see only their assigned jobs + const workerJobs = workerJobIds || [] + return [...workerJobs].sort((a, b) => { + const aNum = Number(a) + const bNum = Number(b) + return bNum - aNum + }) + } else { + // Employers see only their posted jobs + const userJobs = userJobIds || [] + return [...userJobs].sort((a, b) => { + const aNum = Number(a) + const bNum = Number(b) + return bNum - aNum + }) + } + }, [userJobIds, workerJobIds, profile]) + + // Debug: Log job counts when they change + useEffect(() => { + if (isConnected && address) { + console.log('[My Jobs] Job counts:', { + userJobs: userJobIds?.length || 0, + workerJobs: workerJobIds?.length || 0, + total: allJobIds.length + }) + } + }, [userJobIds, workerJobIds, allJobIds.length, isConnected, address]) + + // Auto-refresh jobs periodically + useEffect(() => { + if (!isConnected || !address) return + + // Initial refetch + const doRefetch = async () => { + setIsRefreshing(true) + try { + await refetch() + } finally { + setIsRefreshing(false) + } + } + + doRefetch() + + // Set up interval to refetch every 15 seconds + const interval = setInterval(() => { + doRefetch() + }, 15000) + + return () => clearInterval(interval) + }, [isConnected, address, refetch]) + + const toggleExpand = (jobId: string) => { + const newExpanded = new Set(expandedJobs) + if (newExpanded.has(jobId)) { + newExpanded.delete(jobId) + } else { + newExpanded.add(jobId) + } + setExpandedJobs(newExpanded) + } + + const handlePlaceBid = async (jobId: bigint) => { + if (reputation.reputationScore < 10) { + // Reputation requirement removed + } + + try { + await placeBid(jobId, bidAmount || '0') + showToast({ + title: "Bid Submitted", + description: "Your bid has been registered on the blockchain", + }) + setBidAmount('') + setShowBidForm(null) + // Wait a bit for transaction to be mined, then refetch + setTimeout(() => { + refetch() + }, 2000) + } catch (error: any) { + showToast({ + title: "Error", + description: error?.message || "Failed to submit bid", + }) + } + } + + const handleAcceptBid = async (jobId: bigint, workerAddress: `0x${string}`) => { + try { + await acceptBid(jobId, workerAddress) + showToast({ + title: "Bid Accepted", + description: "The worker has been assigned to the job", + }) + // Wait a bit for transaction to be mined, then refetch + setTimeout(() => { + refetch() + }, 2000) + } catch (error: any) { + showToast({ + title: "Error", + description: error?.message || "Failed to accept bid", + }) + } + } + + const handleCompleteJob = async (jobId: bigint) => { + try { + await completeJob(jobId) + showToast({ + title: "Job Completed", + description: "Payment has been released and your reputation has increased", + }) + // Wait a bit for transaction to be mined, then refetch + setTimeout(() => { + refetch() + }, 2000) + } catch (error: any) { + showToast({ + title: "Error", + description: error?.message || "Failed to complete job", + }) + } + } + + const handleCancelJob = async (jobId: bigint) => { + if (!confirm('Are you sure you want to cancel this job? The payment will be refunded.')) { + return + } + + try { + await cancelJob(jobId) + showToast({ + title: "Job Cancelled", + description: "Payment has been refunded", + }) + // Wait a bit for transaction to be mined, then refetch + setTimeout(() => { + refetch() + }, 2000) + } catch (error: any) { + showToast({ + title: "Error", + description: error?.message || "Failed to cancel job", + }) + } + } + + if (!isConnected || !address) { + return ( +
+ +
+ +

Connect Your Wallet

+

Connect your wallet to view your jobs

+
+ +
+
+
+
+
+ ) + } + + return ( +
+ +
+
+ {/* Header */} + +
+

+ My Jobs +

+
+

+ Manage all your onchain jobs • {allJobIds.length} jobs +

+ {isRefreshing && ( + + )} +
+
+ {profile === 'employer' && ( + + + + Post Job + + + )} +
+ + {/* Stats - Profile specific */} +
+ {profile === 'worker' ? ( + <> + +
+ +
+

Assigned Jobs

+

{workerJobIds?.length || 0}

+
+
+
+ +
+ +
+

Reputation

+

{reputation.reputationScore}

+
+
+
+ + ) : ( + <> + +
+ +
+

Posted Jobs

+

{userJobIds?.length || 0}

+
+
+
+ +
+ +
+

Active Listings

+

{userJobIds?.length || 0}

+
+
+
+ + )} +
+ + {/* Jobs List */} + {allJobIds.length === 0 ? ( + + +

+ {profile === 'worker' + ? "You don't have any assigned jobs yet" + : "You haven't posted any jobs yet"} +

+ {profile === 'employer' && ( + + + + Post First Job + + + )} + {profile === 'worker' && ( + + + Browse Available Jobs + + + )} +
+ ) : ( +
+ {allJobIds.map((jobId, index) => ( + toggleExpand(jobId.toString())} + onPlaceBid={handlePlaceBid} + onAcceptBid={handleAcceptBid} + onCompleteJob={handleCompleteJob} + onCancelJob={handleCancelJob} + isPlacingBid={isPlacingBid} + isAcceptingBid={isAcceptingBid} + isCompletingJob={isCompletingJob} + isCancellingJob={isCancellingJob} + reputation={reputation.reputationScore} + bidAmount={bidAmount} + setBidAmount={setBidAmount} + showBidForm={showBidForm === jobId} + setShowBidForm={(show) => setShowBidForm(show ? jobId : null)} + /> + ))} +
+ )} +
+
+
+
+ ) +} + +// Component for each job with all actions +function JobCardWithActions({ + jobId, + address, + userJobIds, + workerJobIds, + isExpanded, + onToggleExpand, + onPlaceBid, + onAcceptBid, + onCompleteJob, + onCancelJob, + isPlacingBid, + isAcceptingBid, + isCompletingJob, + isCancellingJob, + reputation, + bidAmount, + setBidAmount, + showBidForm, + setShowBidForm, +}: { + jobId: bigint + address: `0x${string}` + userJobIds: readonly bigint[] + workerJobIds: readonly bigint[] + isExpanded: boolean + onToggleExpand: () => void + onPlaceBid: (jobId: bigint) => void + onAcceptBid: (jobId: bigint, worker: `0x${string}`) => void + onCompleteJob: (jobId: bigint) => void + onCancelJob: (jobId: bigint) => void + isPlacingBid: boolean + isAcceptingBid: boolean + isCompletingJob: boolean + isCancellingJob: boolean + reputation: number + bidAmount: string + setBidAmount: (amount: string) => void + showBidForm: boolean + setShowBidForm: (show: boolean) => void +}) { + const { job, isLoading } = useJob(jobId) + const { bids, isLoading: bidsLoading } = useJobBids(jobId) + + if (isLoading || !job) { + return ( + +
+
+
+
+
+ ) + } + + const isEmployer = address.toLowerCase() === job.employer.toLowerCase() + const isWorker = address.toLowerCase() === job.worker.toLowerCase() + const isAssigned = job.worker !== '0x0000000000000000000000000000000000000000' + const canBid = !isEmployer && !isAssigned && !job.completed && !job.cancelled + // Reputation requirement removed - workers can bid with any reputation level + const isMyJob = userJobIds.includes(jobId) + const isMyAssignedJob = workerJobIds.includes(jobId) + + const statusColor = job.completed + ? 'text-mx-green' + : job.cancelled + ? 'text-red-500' + : isAssigned + ? 'text-somnia-cyan' + : 'text-yellow-500' + + const statusText = job.completed + ? 'Completed' + : job.cancelled + ? 'Cancelled' + : isAssigned + ? 'Assigned' + : 'Open' + + return ( + + {/* Header */} +
+
+
+
+

{job.title}

+ + {statusText} + + {isMyJob && ( + + My Job + + )} + {isMyAssignedJob && ( + + Assigned to Me + + )} +
+
+
+ + {job.location} +
+
+ + {formatEther(job.reward)} STT +
+
+ + + {formatDistanceToNow(new Date(Number(job.deadline) * 1000), { + addSuffix: true, + locale: enUS, + })} + +
+
+
+
+ + + + + + + {isExpanded ? ( + + ) : ( + + )} + +
+
+
+ + {/* Expanded Actions */} + {isExpanded && ( + +
+ {/* Actions for Employer */} + {isEmployer && !job.completed && !job.cancelled && ( +
+

+ + Actions as Employer +

+ + {/* Cancel Job */} + onCancelJob(jobId)} + disabled={isCancellingJob} + className="w-full px-4 py-3 bg-red-500/20 hover:bg-red-500/30 rounded-xl text-white font-bold border border-red-500/30 disabled:opacity-50 flex items-center justify-center gap-2" + > + + {isCancellingJob ? 'Cancelling...' : 'Cancel Job'} + + + {/* Accept Bids */} + {bids && bids.length > 0 && !isAssigned && ( +
+

Received Bids ({bids.length}):

+ {bids.map((bid: any, idx: number) => ( +
+
+

+ {bid.worker.slice(0, 6)}...{bid.worker.slice(-4)} +

+ {bid.amount > 0n && ( +

+ Bid: {formatEther(bid.amount)} STT +

+ )} +
+ onAcceptBid(jobId, bid.worker as `0x${string}`)} + disabled={isAcceptingBid || bid.accepted} + className="px-4 py-2 bg-gradient-to-r from-mx-green to-somnia-cyan rounded-lg text-white font-bold text-sm disabled:opacity-50 flex items-center gap-2" + > + + {bid.accepted ? 'Accepted' : isAcceptingBid ? 'Accepting...' : 'Accept'} + +
+ ))} +
+ )} +
+ )} + + {/* Actions for Worker */} + {isWorker && !job.completed && !job.cancelled && ( +
+

+ + Actions as Worker +

+ onCompleteJob(jobId)} + disabled={isCompletingJob} + className="w-full px-4 py-3 bg-gradient-to-r from-mx-green to-emerald-400 rounded-xl text-white font-bold shadow-neural-glow disabled:opacity-50 flex items-center justify-center gap-2" + > + + {isCompletingJob ? 'Completing...' : 'Complete Job'} + +
+ )} + + {/* Actions for Bidding */} + {canBid && ( +
+

+ + Place Bid +

+ {!showBidForm ? ( + setShowBidForm(true)} + disabled={false} + className="w-full px-4 py-3 bg-gradient-to-r from-somnia-purple to-mx-green rounded-xl text-white font-bold shadow-neural-glow disabled:opacity-50 flex items-center justify-center gap-2" + > + + Place Bid + + ) : ( +
+ setBidAmount(e.target.value)} + placeholder="Bid amount (optional)" + className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50" + /> +
+ onPlaceBid(jobId)} + disabled={isPlacingBid} + className="flex-1 px-4 py-2 bg-gradient-to-r from-somnia-purple to-mx-green rounded-lg text-white font-bold disabled:opacity-50" + > + {isPlacingBid ? 'Submitting...' : 'Submit Bid'} + + setShowBidForm(false)} + className="px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white" + > + Cancel + +
+
+ )} +
+ )} + + {/* Job Info */} +
+
+
+

Employer

+

+ {job.employer.slice(0, 6)}...{job.employer.slice(-4)} +

+
+ {isAssigned && ( +
+

Worker

+

+ {job.worker.slice(0, 6)}...{job.worker.slice(-4)} +

+
+ )} +
+

Created

+

+ {formatDistanceToNow(new Date(Number(job.createdAt) * 1000), { + addSuffix: true, + locale: enUS, + })} +

+
+
+

ID

+

#{jobId.toString()}

+
+
+
+
+
+ )} +
+ ) +} + diff --git a/src/app/gigstream/page.tsx b/src/app/gigstream/page.tsx index 569f87e..59310ad 100644 --- a/src/app/gigstream/page.tsx +++ b/src/app/gigstream/page.tsx @@ -4,7 +4,7 @@ import { motion } from 'framer-motion' import { useAccount } from 'wagmi' import Link from 'next/link' -import { MapPin, DollarSign, Clock, Zap, Plus, Brain } from 'lucide-react' +import { MapPin, DollarSign, Clock, Zap, Plus, Brain, Briefcase, User, Search, CheckCircle } from 'lucide-react' import { useEffect, useState } from 'react' import Navbar from '@/components/somnia/Navbar' import Footer from '@/components/somnia/Footer' @@ -12,14 +12,26 @@ import AIInsightsPanel from '@/components/gigstream/AIInsightsPanel' import AIJobMatcher from '@/components/gigstream/AIJobMatcher' import AIBidOptimizer from '@/components/gigstream/AIBidOptimizer' import GeminiBot from '@/components/chatbot/GeminiBot' +import ProfileToggle from '@/components/gigstream/ProfileToggle' +import ContractAddressVerifier from '@/components/gigstream/ContractAddressVerifier' import { useGigStream } from '@/hooks/useGigStream' +import { useSDSJobs } from '@/hooks/useSDSJobs' import JobCard from '@/components/gigstream/JobCard' +import SDSJobsIndicator from '@/components/gigstream/SDSJobsIndicator' +import LiveEventsPanel from '@/components/gigstream/LiveEventsPanel' + +type ProfileType = 'worker' | 'employer' export default function GigStreamDashboard() { const { address, isConnected } = useAccount() - const { jobCounter, refetch } = useGigStream() + const { jobCounter, refetch, userJobIds, workerJobIds, reputation } = useGigStream() const [jobsCount, setJobsCount] = useState(0) + const [profile, setProfile] = useState('worker') + const [searchQuery, setSearchQuery] = useState('') + + // Fetch jobs from Somnia Data Streams (optional, for enrichment) + const { jobs: sdsJobs } = useSDSJobs(address, isConnected) // Fetch all jobs from contract useEffect(() => { @@ -56,44 +68,176 @@ export default function GigStreamDashboard() {
) : ( -
+
{/* Header */} -
-

+
+
+

GigStream Dashboard

-

+

+

Live SDS Streams • {jobsCount} active jobs

+ {sdsJobs.length > 0 && ( + + )} +
- +
+ + {profile === 'employer' && ( + - + Post Job + )} +
+
+ + {/* Profile-specific stats */} + + {profile === 'worker' ? ( + <> +
+
+ + Reputation +
+

{reputation.reputationScore} pts

+

+ Can place bids +

+
+
+
+ + My Jobs +
+

{workerJobIds?.length || 0}

+

Assigned jobs

+
+
+
+ + Completed +
+

{reputation.jobsCompleted}

+

Jobs finished

+
+ + ) : ( + <> +
+
+ + Posted Jobs +
+

{userJobIds?.length || 0}

+

Active listings

+
+
+
+ + Available +
+

{jobsCount}

+

Total jobs

+
+
+
+ + Quick Post +
+ + + Post Now + + +
+ + )} +
+ + {/* Search bar for workers */} + {profile === 'employer' && ( + +
+ + setSearchQuery(e.target.value)} + placeholder="Search jobs by title or location..." + className="flex-1 bg-white/10 border border-white/20 rounded-lg sm:rounded-xl px-3 sm:px-4 py-1.5 sm:py-2 text-white placeholder-white/50 backdrop-blur-xl focus:outline-none focus:border-somnia-purple/50 text-sm sm:text-base" + /> +
+
+ )} - {/* Live Jobs Grid */} - {jobsCount > 0 ? ( -
+ {/* Live Jobs Grid - Filtered by profile */} + {profile === 'worker' && jobsCount > 0 ? ( +
+
+

Available Jobs

+

+ Showing {Math.min(Number(jobsCount), 12)} of {jobsCount} +

+
+
+ {Array.from({ length: Math.min(Number(jobsCount), 12) }, (_, i) => { + const jobId = BigInt(Number(jobsCount) - i) + return + })} +
+
+ ) : profile === 'employer' && jobsCount > 0 ? ( +
+
+

All Jobs

+

+ Showing {Math.min(Number(jobsCount), 12)} of {jobsCount} +

+
+
{Array.from({ length: Math.min(Number(jobsCount), 12) }, (_, i) => { const jobId = BigInt(Number(jobsCount) - i) return })} +
+
+ ) : profile === 'worker' ? ( +
+

No jobs available yet

+

Check back later for new opportunities

) : (
-

No hay trabajos disponibles aún

+

No jobs posted yet

- Publicar Primer Trabajo + Post First Job
)} - {/* AI Features Section */} -
-
-
- + {/* AI Features Section - Profile specific */} + {profile === 'worker' && ( +
+
+
+
-

AI-Powered Features

-

Powered by Google Gemini

+

AI-Powered Features

+

Powered by Google Gemini

- {/* AI Insights Panel */} - - - {/* AI Tools Grid */} -
+ {/* AI Tools for Workers */} +
+ )} - {/* Quick Links */} -
+ {profile === 'employer' && ( +
+
+
+ +
+
+

AI-Powered Insights

+

Powered by Google Gemini

+
+
+ + {/* AI Insights for Employers */} + +
+ )} + + {/* Quick Links - Profile specific */} +
+ {profile === 'worker' && ( + + +

My Jobs

+

Manage assigned jobs

+
+ + )} + {profile === 'employer' && ( + + +

My Posted Jobs

+

Manage your job listings

+
+ + )} -

My Profile

-

View reputation and statistics

+

My Profile

+

+ {profile === 'worker' ? 'View reputation and stats' : 'View profile and stats'} +

-
-

Live Streams

-

SDS active • {jobsCount} events

-
+ {profile === 'worker' && ( + + +

Reputation Help

+

Learn about reputation

+
+ + )} +
)} diff --git a/src/app/gigstream/post/page.tsx b/src/app/gigstream/post/page.tsx index c481d99..780d28b 100644 --- a/src/app/gigstream/post/page.tsx +++ b/src/app/gigstream/post/page.tsx @@ -8,8 +8,11 @@ import { useAccount, useSendTransaction, useBalance } from 'wagmi' import { parseEther, formatEther } from 'viem' import { useGemini } from '@/providers/GeminiProvider' import { useToast } from '@/components/ui/use-toast' +import { useGigStream } from '@/hooks/useGigStream' +import { useTransactionNotification } from '@/hooks/useTransactionNotification' import Navbar from '@/components/somnia/Navbar' import Footer from '@/components/somnia/Footer' +import LocationSelector from '@/components/gigstream/LocationSelector' export default function PostJob() { const [isPending, startTransition] = useTransition() @@ -17,6 +20,8 @@ export default function PostJob() { const { sendTransactionAsync } = useSendTransaction() const { generateText } = useGemini() const { showToast } = useToast() + const { jobCounter } = useGigStream() + const { showSuccess, showError, showPending, NotificationComponents } = useTransactionNotification() // Check user balance const { data: balance, isLoading: balanceLoading } = useBalance({ @@ -43,11 +48,11 @@ export default function PostJob() { setGeminiSuggestions([]) try { const suggestions = await generateText(` - You are a freelance expert Mexico. User in: ${formData.location} - Skills in demand: plumber, electrician, taquero, DJ events, production crew + You are a freelance expert in global freelance markets. User location: ${formData.location} + Skills in demand: plumber, electrician, DJ events, production crew, handyman, technician - Suggest 3 job titles + reward ranges for: "${formData.title || 'service'}" - Respond ONLY with a valid JSON array: ["Emergency Plumber CDMX (300-800 STT)", "Electrician Guadalajara (450-1100 STT)", "DJ Corporate Event CDMX (600-1500 STT)"] + Suggest 3 job titles + reward ranges for: "${formData.title || 'service'}" in ${formData.location} + Respond ONLY with a valid JSON array: ["Emergency Plumber (300-800 STT)", "Electrician (450-1100 STT)", "DJ Corporate Event (600-1500 STT)"] No markdown, no explanations, just the JSON array. `) @@ -194,19 +199,19 @@ export default function PostJob() { // Ensure address is properly formatted (trim and validate) const contractAddress = GIGESCROW_ADDRESS.trim() as `0x${string}` if (!contractAddress || contractAddress === '0x0000000000000000000000000000000000000000') { - showToast({ - title: "Error", - description: "Contract not deployed. Configure NEXT_PUBLIC_GIGESCROW_ADDRESS" - }) + showError( + "Contract Error", + "Contract not deployed. Configure NEXT_PUBLIC_GIGESCROW_ADDRESS" + ) return } // Validate address format if (!contractAddress.startsWith('0x') || contractAddress.length !== 42) { - showToast({ - title: "Error", - description: "Invalid contract address format" - }) + showError( + "Invalid Contract", + "Invalid contract address format" + ) return } @@ -216,10 +221,10 @@ export default function PostJob() { // Validate deadline is at least 1 day in the future (contract requirement) const minDeadline = Math.floor(Date.now() / 1000) + 86400 // 1 day if (deadlineTimestamp < minDeadline) { - showToast({ - title: "Invalid deadline", - description: "Deadline must be at least 1 day from now" - }) + showError( + "Invalid Deadline", + "Deadline must be at least 1 day from now" + ) return } @@ -238,67 +243,90 @@ export default function PostJob() { ] }) + // Show pending notification + showPending( + "Posting Job...", + "Please confirm the transaction in your wallet" + ) + const hash = await sendTransactionAsync({ to: contractAddress, value: rewardAmount, data: data as `0x${string}`, }) - showToast({ - title: "Job posted!", - description: `Transaction submitted: ${hash.slice(0, 10)}...`, - duration: 5000 - }) + // Show success notification with transaction hash + showSuccess( + "Job Posted Successfully!", + "Your job has been posted to the blockchain and will appear in the marketplace shortly.", + hash as string, + 6000 + ) + + // Note: Job will be automatically published to Somnia Data Streams + // via the /api/streams endpoint when it detects the JobPosted event + // This happens in the background and enriches the data with structured streams // Redirect to dashboard after successful post setTimeout(() => { window.location.href = '/gigstream' - }, 2000) + }, 3000) } catch (error: any) { console.error('Error posting job:', error) + // Extract transaction hash if available + const txHash = error?.transactionHash || error?.hash || error?.data?.hash + // Provide user-friendly error messages + let errorTitle = "Transaction Failed" let errorMessage = "Failed to post job" + if (error?.message?.includes('User rejected')) { - errorMessage = "Transaction was cancelled" + errorTitle = "Transaction Cancelled" + errorMessage = "You cancelled the transaction in your wallet" } else if (error?.message?.includes('insufficient funds') || error?.message?.includes('balance')) { - errorMessage = "Insufficient balance. Please check your STT balance." + errorTitle = "Insufficient Balance" + errorMessage = "You don't have enough STT. Please check your balance and try again." } else if (error?.message?.includes('Internal JSON-RPC error')) { - errorMessage = "Network error. Please check your connection and try again." + errorTitle = "Network Error" + errorMessage = "Network connection error. Please check your connection and try again." } else if (error?.message) { errorMessage = error.message } - showToast({ - title: "Error", - description: errorMessage - }) + showError( + errorTitle, + errorMessage, + txHash, + 10000 + ) } }) } return (
+ {NotificationComponents}
{/* Header */} -
- -
-

+
+ +
+

Post Job

-

Live on SDS streams in 2s

+

Live on SDS streams in 2s

@@ -306,7 +334,7 @@ export default function PostJob() { {isGenerating && ( @@ -322,15 +350,15 @@ export default function PostJob() { animate={{ rotate: 360 }} transition={{ repeat: Infinity, duration: 1, ease: 'linear' }} > - + - Gemini generating... + Gemini generating... ) : ( <> - - Gemini AI Suggestions - + + Gemini AI Suggestions + AI @@ -339,56 +367,57 @@ export default function PostJob() { {/* Form */} - + {/* Title */} -
-

- EVM Compatible: Use Solidity, Hardhat, Foundry as-is. - Build on GigStream MX with familiar tools. + Built with Hardhat and Solidity 0.8.29. + EVM Compatible: Use existing tools like Remix, VSCode, Foundry, or Hardhat. + Build on GigStream with familiar tools.

@@ -89,10 +90,15 @@ export default function DevelopersSection() { transition={{ delay: idx * 0.1 }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} - className="px-8 py-4 backdrop-blur-xl bg-white/5 border border-somnia-cyan/20 rounded-xl text-white font-medium hover:border-somnia-cyan/50 transition-all flex items-center space-x-2" + className={`px-8 py-4 backdrop-blur-xl ${ + (item as any).featured + ? 'bg-gradient-to-r from-somnia-cyan/20 to-somnia-purple/20 border-somnia-cyan/40' + : 'bg-white/5 border-somnia-cyan/20' + } border rounded-xl text-white font-medium hover:border-somnia-cyan/50 transition-all flex items-center space-x-2`} > {item.name} + {(item as any).featured && Used} ))} diff --git a/src/components/somnia/Footer.tsx b/src/components/somnia/Footer.tsx index 27cc6b9..7795306 100644 --- a/src/components/somnia/Footer.tsx +++ b/src/components/somnia/Footer.tsx @@ -3,10 +3,9 @@ import { useState, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' import Link from 'next/link' -import { ArrowUp, Mail, Github, Twitter, MessageCircle, Youtube, Send } from 'lucide-react' +import { ArrowUp, Twitter, ExternalLink } from 'lucide-react' export default function Footer() { - const [email, setEmail] = useState('') const [showBackToTop, setShowBackToTop] = useState(false) useEffect(() => { @@ -21,50 +20,34 @@ export default function Footer() { window.scrollTo({ top: 0, behavior: 'smooth' }) } - const handleNewsletter = (e: React.FormEvent) => { - e.preventDefault() - // Newsletter signup logic - setEmail('') - } - - const companyLinks = [ - { name: 'About', href: '#about' }, - { name: 'Careers', href: '#careers' }, - { name: 'Blog', href: '#blog' }, - { name: 'Whitepaper PDF', href: '/whitepaper.pdf' } - ] - const productLinks = [ - { name: 'Technology', href: '#technology' }, - { name: 'Ecosystem', href: '#ecosystem' }, - { name: 'SOMI Token', href: '#somi' }, - { name: 'Developers', href: '#developers' }, - { name: 'Roadmap', href: '#roadmap' } + { name: 'Dashboard', href: '/gigstream' }, + { name: 'Marketplace', href: '/gigstream/marketplace' }, + { name: 'Post Job', href: '/gigstream/post' }, + { name: 'My Jobs', href: '/gigstream/my-jobs' }, + { name: 'Profile', href: '/gigstream/profile' } ] - const communityLinks = [ - { name: 'Discord', href: 'https://discord.gg/somnia', icon: MessageCircle }, - { name: 'Twitter/X', href: 'https://twitter.com/somnia_network', icon: Twitter }, - { name: 'GitHub', href: 'https://github.com/somnia-network', icon: Github }, - { name: 'YouTube', href: '#', icon: Youtube } + const resourcesLinks = [ + { name: 'Live Demo', href: 'https://gigstream-mx.vercel.app', external: true }, + { name: 'Smart Contracts', href: 'https://shannon-explorer.somnia.network/address/0x8D742671508E1C5BFF77f3d0AE70218C8Cc57Cef', external: true }, + { name: 'Somnia Docs', href: 'https://docs.somnia.network', external: true }, + { name: 'Somnia Explorer', href: 'https://shannon-explorer.somnia.network', external: true } ] - const legalLinks = [ - { name: 'Privacy Policy', href: '#privacy' }, - { name: 'Terms', href: '#terms' }, - { name: 'KYC/AML', href: '#kyc' }, - { name: 'MiCA Compliance', href: '#mica' } + const socialLinks = [ + { name: 'X (Twitter)', href: 'https://twitter.com/somnia_network', icon: Twitter } ] return (