diff --git a/.codexignore b/.codexignore new file mode 100644 index 00000000..d549b3a2 --- /dev/null +++ b/.codexignore @@ -0,0 +1,4 @@ +.env +.env.* +**/*.env**/*.env.* +agent/.env \ No newline at end of file diff --git a/.env.mainnet.example b/.env.mainnet.example index 8be73f11..40c24031 100644 --- a/.env.mainnet.example +++ b/.env.mainnet.example @@ -1,9 +1,21 @@ # Fork source MAINNET_RPC_URL=... -# Deployer +# Deployer (use DEPLOYER_PK directly or resolve it at runtime via agent/with-signer.mjs) DEPLOYER_PK=0x... +# Optional signer resolution (used with agent/with-signer.mjs) +# SIGNER_TYPE=env +# PRIVATE_KEY=0x... +# KEYSTORE_PATH=./keys/deployer.json +# KEYSTORE_PASSWORD=... +# KEYCHAIN_SERVICE=og-deployer +# KEYCHAIN_ACCOUNT=deployer +# VAULT_ADDR=https://vault.example.com +# VAULT_TOKEN=... +# VAULT_SECRET_PATH=secret/data/og-deployer +# VAULT_SECRET_KEY=private_key + # Optimistic Governor config OG_COLLATERAL=0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 OG_BOND_AMOUNT=250000000 diff --git a/.gitignore b/.gitignore index 02854013..215ea636 100644 --- a/.gitignore +++ b/.gitignore @@ -8,14 +8,17 @@ out/ /broadcast/**/dry-run/ /broadcast/* -# Docs -docs/ - # Dotenv file .env .env.mainnet .env.sepolia +agent/.env # Anvil files anvil.log -anvil.pid \ No newline at end of file +anvil.pid + +# Node modules +node_modules +frontend/node_modules +agent/node_modules diff --git a/AGENTS.md b/AGENTS.md index 04bd273d..92c67051 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,23 @@ # Repository Guidelines +## Documentation Hierarchy +- Keep contribution guidance in both human docs and machine-readable instructions. +- `AGENTS.md`: normative instructions for coding agents and automation. +- `README.md`: architecture, intent, and operational context for humans. +- `CONTRIBUTING.md`: shared contributor workflow and policy across the repo. +- `skills/add-agent-commitment/SKILL.md`: reusable workflow for adding new agent/commitment combos. +- Precedence when instructions conflict: +1. Closest file to the changed code path wins. +2. `AGENTS.md` is authoritative for agent behavior. +3. Root-level files apply unless overridden by a closer area-level file. + ## Project Structure & Module Organization - **`src/`**: Core Solidity contracts (e.g., `Counter.sol`). Keep new modules grouped by domain and include SPDX + pragma headers. - **`script/`**: Deployment and automation scripts (e.g., `Counter.s.sol`, `DeploySafeWithOptimisticGovernor.s.sol`). Favor reusable helpers and parameterize via environment variables. - **`test/`**: Forge tests using `forge-std`’s `Test` base. Mirror contract names (`.t.sol`) and co-locate fixtures with the subject under test. - **`lib/`**: External dependencies (currently `forge-std`) managed through Foundry. +- **`agent/`**: Shared offchain runner, signer integrations, and reusable tooling. +- **`agent-library/`**: Agent-specific implementations under `agent-library/agents//`. ## Build, Test, and Development Commands - `forge build`: Compile all contracts. @@ -20,12 +33,22 @@ - Keep files focused and small; prefer internal helpers over inline duplication. - Run `forge fmt` to enforce style; include SPDX identifiers and explicit visibility where practical. - Use descriptive variable names (avoid single letters outside of loop counters or hashes). +- For Node.js code in `agent/` and `agent-library/`, keep modules small and isolate side effects at the edges. + +## Agent Locality Rule +- New functionality for a specific agent must be implemented in that agent's own files under `agent-library/agents//`. +- Do not add agent-specific behavior to shared generalized files in `agent/src/lib/` or `agent/src/index.js`. +- Shared generalized files should only change when: +1. The change is required for multiple agents. +2. The change fixes a bug in shared infrastructure. +- If a pull request changes shared generalized agent files, include a brief cross-agent rationale in the PR description and link impacted agents. ## Testing Guidelines - Use `forge-std/Test` utilities for assertions, fuzzing (`testFuzz_*`), and logging. - Name tests with behavior-first patterns (`test_IncrementsCounter`, `testFuzz_SetNumberMaintainsState`). - Cover success, failure, and access-control paths; add revert expectation tests when changing critical flows. - When modifying gas-sensitive code, refresh `forge snapshot` and include notes in PRs. +- For `agent-library` changes, run the relevant agent test/simulation scripts and note commands in the PR. ## Commit & Pull Request Guidelines - Write imperative, concise commit messages (e.g., "Add OG deployment script"); group related changes together. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..80aa2d58 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,47 @@ +# Contributing + +This repository uses two kinds of contribution guidance: + +- `AGENTS.md` for machine-targeted, normative instructions. +- `README.md` for human-oriented architecture and workflow context. + +## Precedence Rules + +When instructions conflict: + +1. The closest file to the code you are editing wins. +2. `AGENTS.md` is authoritative for agent behavior. +3. Root-level guidance applies when there is no closer override. + +## Directory Guidance + +- `src/`, `script/`, `test/`: Solidity contracts, scripts, and tests. +- `agent/`: shared offchain runner and reusable agent infrastructure. +- `agent-library/agents//`: agent-specific implementations. +- `docs/`: operational and architecture docs. + +## Required Contributor Workflow + +1. Read relevant local docs before editing (`AGENTS.md`, `README.md`). +2. Keep changes scoped to the correct area. +Agent-specific behavior belongs in `agent-library/agents//`. +Shared runner changes in `agent/` require cross-agent justification. +3. Run the minimum required checks. +Solidity changes: `forge fmt`, `forge test`. +Agent changes: relevant module tests/simulations. +4. In PRs, document: +What changed and why. +Tests run. +Any config or environment variable impacts. + +## Agent Extension Policy + +New agent behavior must be added to that agent's own library files instead of shared generalized files. + +- Preferred location: `agent-library/agents//agent.js` and related files in that directory. +- Shared files such as `agent/src/lib/*` and `agent/src/index.js` should only change for multi-agent abstractions or shared bug fixes. +- If shared files are changed, the PR must include: +Why an agent-local implementation was insufficient. +Which existing agents are impacted. + +See `docs/agent-extension-guidelines.md` for the decision framework and examples. diff --git a/README.md b/README.md index 0cd0d6cd..012908c6 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,23 @@ -## OG Deployer +# Oya Commitments -Command-line tooling for deploying onchain Commitments: a Safe with an Optimistic Governor module configured with natural language rules. Use it to spin up a new Safe, connect the Optimistic Governor module, and set the rules/bond/collateral in one script run. +Oya Commitments are Safe-based commitments controlled by plain-language rules and enforced via an Optimistic Governor module. This repo contains the Solidity contracts, deployment scripts, an optional web UI, and an offchain agent scaffold. -## What This Repo Does +## Beta Disclaimer -- Deploys a Safe and the Optimistic Governor module in one flow. -- Encodes natural language rules into the module configuration. -- Supports env-var overrides for Safe and OG parameters. -- Uses Foundry for scripting, testing, and deployments. +This is beta software provided “as is.” Use at your own risk. No guarantees of safety, correctness, or fitness for any purpose. + +## How It Works (At a Glance) + +- Write plain-language rules that define what the commitment may do. +- Deploy a Safe wired to an Optimistic Governor module with those rules. +- An agent or user proposes transactions via the module and posts a bond. +- If no challenge occurs during the window, the proposal is executed by the Safe. ## Quick Start 1. Install Foundry: https://book.getfoundry.sh/ -2. Set environment variables in your shell or `.env` file (load via `direnv` or `dotenvx` if desired). -3. Run the deployment script. +2. Set required environment variables (see `docs/deployment.md`). +3. Run the deployment script: ```shell forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ @@ -22,218 +26,22 @@ forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimis --private-key ``` -## Required Environment Variables - -- `DEPLOYER_PK`: Private key for the deployer. -- `OG_COLLATERAL`: Address of the ERC20 collateral token. -- `OG_BOND_AMOUNT`: Bond amount for challenges. -- `OG_RULES`: Natural language rules for the commitment. - -## Optional Overrides - -- `SAFE_SALT_NONCE`, `SAFE_THRESHOLD`, `SAFE_OWNERS` -- `OG_SALT_NONCE`, `OG_CHALLENGE_PERIOD`, `OG_RULES_URI` -- `OG_MASTER_COPY`, `SAFE_SINGLETON`, `SAFE_FALLBACK_HANDLER` -- `MODULE_PROXY_FACTORY` - -## Web Frontend - -The web frontend is a lightweight UI for filling in Safe + Optimistic Governor parameters and launching the same deployment flow as the script. It can be hosted as a static site and uses RPC endpoints to read chain state and craft the deployment payloads. - -### Dependencies - -- Node.js 18+ (or newer) -- npm, pnpm, or yarn for package management - -### Local Development - -From the web frontend directory (if you keep it alongside this repo), install dependencies and start the dev server: - -```shell -npm install -npm run dev -``` - -### Required Environment Variables - -Expose these values to the frontend build (for example via `.env` in the frontend project) so the UI can prefill defaults and target the correct network: - -- `MAINNET_RPC_URL` or `SEPOLIA_RPC_URL` (or another network-specific RPC URL) -- Default addresses (optional but recommended for prefill): - - `SAFE_SINGLETON` - - `SAFE_PROXY_FACTORY` - - `SAFE_FALLBACK_HANDLER` - - `OG_MASTER_COPY` - - `MODULE_PROXY_FACTORY` - -### Form Fields → On-Chain Parameters - -Use the same inputs as the deploy script; the UI should map them directly to the on-chain deployment parameters: - -- **Safe Owners** → `SAFE_OWNERS` -- **Safe Threshold** → `SAFE_THRESHOLD` -- **Safe Salt Nonce** → `SAFE_SALT_NONCE` -- **OG Collateral Token** → `OG_COLLATERAL` -- **OG Bond Amount** → `OG_BOND_AMOUNT` -- **OG Rules (Natural Language)** → `OG_RULES` -- **OG Challenge Period** → `OG_CHALLENGE_PERIOD` -- **OG Rules URI** → `OG_RULES_URI` -- **OG Salt Nonce** → `OG_SALT_NONCE` -- **Safe Singleton** → `SAFE_SINGLETON` -- **Safe Proxy Factory** → `SAFE_PROXY_FACTORY` -- **Safe Fallback Handler** → `SAFE_FALLBACK_HANDLER` -- **OG Master Copy** → `OG_MASTER_COPY` -- **Module Proxy Factory** → `MODULE_PROXY_FACTORY` - -### Deployment Note - -Build output is static (e.g., `dist/` or `build/`, depending on your frontend tooling) and can be hosted on any static host (Netlify, Vercel static output, S3/CloudFront, etc.). Ensure the RPC URLs and default addresses are configured for the target network before deploying the static bundle. - -## Example `.env` - -```ini -# Required -DEPLOYER_PK=0xabc123... -OG_COLLATERAL=0x1111111111111111111111111111111111111111 -OG_BOND_AMOUNT=250000000 -OG_RULES="Any assets deposited in this Commitment may be transferred back to the depositor before January 15th, 2026 (12:00AM PST). After the deadline, assets may only be transferred to jdshutt.eth. If a third party is initiating the transfer after the deadline, they may take a 10% cut of the assets being transferred as a fee." - -# Safe overrides -SAFE_OWNERS=0x2222222222222222222222222222222222222222,0x3333333333333333333333333333333333333333 -SAFE_THRESHOLD=2 -SAFE_SALT_NONCE=12345 - -# Optimistic Governor overrides -OG_CHALLENGE_PERIOD=604800 -OG_RULES_URI=ipfs://bafy... -OG_SALT_NONCE=67890 - -# Optional factory / master copy overrides -MODULE_PROXY_FACTORY=0x4444444444444444444444444444444444444444 -OG_MASTER_COPY=0x5555555555555555555555555555555555555555 -SAFE_SINGLETON=0x6666666666666666666666666666666666666666 -SAFE_FALLBACK_HANDLER=0x7777777777777777777777777777777777777777 -``` - -## Common Commands - -```shell -forge build -forge test -forge fmt -``` - -## Local Testing - -Dry-run (no broadcast): - -```shell -anvil -forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ - --rpc-url http://127.0.0.1:8545 \ - --private-key -``` - -Broadcast on Anvil: - -```shell -anvil -forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ - --rpc-url http://127.0.0.1:8545 \ - --broadcast \ - --private-key -``` - -## Propose & Execute Transfers - -Propose a transfer (posts the UMA bond via the Optimistic Governor): - -```shell -export PROPOSER_PK= -export OG_MODULE= -export TRANSFER_ASSET= -export TRANSFER_AMOUNT= -export TRANSFER_DESTINATION= - -forge script script/ProposeCommitmentTransfer.s.sol:ProposeCommitmentTransfer \ - --rpc-url \ - --broadcast \ - --private-key $PROPOSER_PK -``` - -Execute a proposal after it passes: - -```shell -export EXECUTOR_PK= -export OG_MODULE= -export PROPOSAL_HASH= -export TRANSFER_ASSET= -export TRANSFER_AMOUNT= -export TRANSFER_DESTINATION= - -forge script script/ExecuteCommitmentTransfer.s.sol:ExecuteCommitmentTransfer \ - --rpc-url \ - --broadcast \ - --private-key $EXECUTOR_PK -``` - -Optional overrides: +## Documentation -- `TRANSFER_OPERATION` (default `0` for `CALL`) -- `TRANSFER_VALUE` (default `0`) +- Contribution workflow and policy: `CONTRIBUTING.md` +- Skill for new agent/commitment combos: `skills/add-agent-commitment/SKILL.md` +- Deployment and configuration: `docs/deployment.md` +- Signer options and `with-signer` helper: `docs/signers.md` +- Offchain agent usage: `docs/agent.md` +- Agent extension decision rules: `docs/agent-extension-guidelines.md` +- Web frontend: `docs/frontend.md` +- Testing and common commands: `docs/testing.md` -### Anvil Test Key + USDC Funding (Fork) +## Repo Layout -Start Anvil with the default test mnemonic and grab one of the printed private keys: - -```shell -anvil --fork-url $MAINNET_RPC_URL --mnemonic "test test test test test test test test test test test junk" -``` - -Fund the test account with USDC by impersonating a whale on the fork: - -```shell -cast rpc anvil_impersonateAccount -cast rpc anvil_setBalance 0x3635C9ADC5DEA00000 -cast send "transfer(address,uint256)" --from -cast rpc anvil_stopImpersonatingAccount -``` - -## Network Env Files - -You can keep per-network env files and load them with a tool like `dotenvx` or `direnv`. - -Mainnet fork example (`.env.mainnet`): - -```ini -MAINNET_RPC_URL=... -DEPLOYER_PK=0x... -OG_COLLATERAL=0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 -OG_BOND_AMOUNT=250000000 -OG_RULES="Any assets deposited in this Commitment may be transferred back to the depositor before January 15th, 2026 (12:00AM PST). After the deadline, assets may only be transferred to jdshutt.eth. If a third party is initiating the transfer after the deadline, they may take a 10% cut of the assets being transferred as a fee." -OG_IDENTIFIER_STR=ASSERT_TRUTH2 -``` - -A ready-to-edit template is available at `.env.mainnet.example`. - -Sepolia example (`.env.sepolia`): - -```ini -SEPOLIA_RPC_URL=... -DEPLOYER_PK=0x... -SAFE_SINGLETON=0x... -SAFE_PROXY_FACTORY=0x... -SAFE_FALLBACK_HANDLER=0x... -OG_MASTER_COPY=0x... -OG_COLLATERAL=0x... -OG_BOND_AMOUNT=... -OG_RULES="Any assets deposited in this Commitment may be transferred back to the depositor before January 15th, 2026 (12:00AM PST). After the deadline, assets may only be transferred to jdshutt.eth. If a third party is initiating the transfer after the deadline, they may take a 10% cut of the assets being transferred as a fee." -``` - -Load the file before running the script: - -```shell -dotenvx run -f .env.mainnet -- forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ - --rpc-url $MAINNET_RPC_URL \ - --private-key $DEPLOYER_PK -``` +- `src/` Solidity contracts +- `script/` Foundry deployment and ops scripts +- `test/` Foundry tests +- `agent/` Offchain agent scaffold +- `frontend/` Web UI +- `lib/` External dependencies (Foundry) diff --git a/agent-library/AGENTS.md b/agent-library/AGENTS.md new file mode 100644 index 00000000..f72e386a --- /dev/null +++ b/agent-library/AGENTS.md @@ -0,0 +1,24 @@ +# Agent Library Guidelines + +## Scope + +This file applies to `agent-library/` and `agent-library/agents/*`. + +## Purpose + +This directory is the home for agent-specific behavior and commitment-specific decision logic. + +## Rules + +- Each agent lives under `agent-library/agents//`. +- Keep commitment logic, prompt strategy, and behavior specialization in that agent's local files. +- Prefer adding new modules over branching shared runner code for one-off behavior. + +## Locality Rule + +When creating a new agent, place functionality in that agent's own files (`agent.js` and adjacent module files). Do not implement single-agent behavior in shared generalized files under `agent/src/lib/` or `agent/src/index.js`. + +## Validation + +- Run module-specific tests/simulations for the changed agent. +- Document test commands in the PR description. diff --git a/agent-library/README.md b/agent-library/README.md new file mode 100644 index 00000000..08569ad0 --- /dev/null +++ b/agent-library/README.md @@ -0,0 +1,17 @@ +# Agent Library + +Each agent lives under `agent-library/agents//` and must include: +- `agent.js`: decision logic and prompt construction. +- `commitment.txt`: plain language commitment that the agent is designed to serve. + +The runner loads the agent module via `AGENT_MODULE` (agent name) and reads the adjacent `commitment.txt`. + +To add a new agent: +1. Copy `agent-library/agents/default/` to a new folder. +2. Update `agent.js` and `commitment.txt`. +3. Set `AGENT_MODULE=`. + +Example agents: +- `agent-library/agents/default/`: generic agent using the commitment text. +- `agent-library/agents/timelock-withdraw/`: timelock withdrawal agent that only withdraws to its own address after the timelock. +- `agent-library/agents/copy-trading/`: copy-trading agent for one configured source trader + market; it reacts to BUY trades only, submits a 99%-of-Safe collateral CLOB order from the configured trading wallet, waits for fill/token receipt, deposits YES/NO tokens to the Safe (direct onchain or via Polymarket relayer), then proposes reimbursement to that same trading wallet for the full Safe collateral snapshot captured at trigger time (1% implied agent fee via reduced copy size). diff --git a/agent-library/agents/copy-trading/agent.js b/agent-library/agents/copy-trading/agent.js new file mode 100644 index 00000000..44ab29b2 --- /dev/null +++ b/agent-library/agents/copy-trading/agent.js @@ -0,0 +1,1083 @@ +import { + CLOB_FAILURE_TERMINAL_STATUS, + CLOB_ORDER_FAILURE_STATUSES, + CLOB_ORDER_FILLED_STATUSES, + CLOB_SUCCESS_TERMINAL_STATUS, + DATA_API_HOST, + DEFAULT_COLLATERAL_TOKEN, + getClobOrder, + getClobTrades, +} from '../../../agent/src/lib/polymarket.js'; +import { erc20Abi, erc1155Abi } from 'viem'; +import { + decodeErc20TransferCallData, + normalizeAddressOrNull, + normalizeHashOrNull, + normalizeTokenId, + parseFiniteNumber, +} from '../../../agent/src/lib/utils.js'; +import { getAlwaysEmitBalanceSnapshotPollingOptions } from '../../../agent/src/lib/polling.js'; +import { resolveRelayerProxyWallet } from '../../../agent/src/lib/polymarket-relayer.js'; + +const COPY_BPS = 9900n; +const FEE_BPS = 100n; +const BPS_DENOMINATOR = 10_000n; +const PRICE_SCALE = 1_000_000n; +const REIMBURSEMENT_SUBMISSION_TIMEOUT_MS = 60_000; + +let copyTradingState = { + seenSourceTradeId: null, + activeSourceTradeId: null, + activeTradeSide: null, + activeTradePrice: null, + activeOutcome: null, + activeTokenId: null, + copyTradeAmountWei: null, + reimbursementAmountWei: null, + reimbursementRecipientAddress: null, + copyOrderId: null, + copyOrderStatus: null, + copyOrderFilled: false, + copyOrderSubmittedMs: null, + orderSubmitted: false, + tokenDeposited: false, + reimbursementProposed: false, + reimbursementProposalHash: null, + reimbursementSubmissionPending: false, + reimbursementSubmissionTxHash: null, + reimbursementSubmissionMs: null, +}; +const normalizeAddress = normalizeAddressOrNull; + +function normalizeOutcome(value) { + if (typeof value !== 'string') return null; + const normalized = value.trim().toLowerCase(); + if (normalized === 'yes') return 'YES'; + if (normalized === 'no') return 'NO'; + return null; +} + +function normalizeTradeSide(value) { + if (typeof value !== 'string') return null; + const normalized = value.trim().toUpperCase(); + return normalized === 'BUY' || normalized === 'SELL' ? normalized : null; +} + +function normalizeTradePrice(value) { + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed <= 0 || parsed >= 1) { + return null; + } + return parsed; +} + +const normalizeHash = normalizeHashOrNull; + +function normalizeOrderId(value) { + if (typeof value !== 'string') return null; + const normalized = value.trim(); + return normalized.length > 0 ? normalized : null; +} + +function normalizeClobStatus(value) { + if (typeof value !== 'string') return null; + const normalized = value.trim().toUpperCase(); + return normalized.length > 0 ? normalized : null; +} + +function hasClobCredentials(config) { + return Boolean( + config?.polymarketClobApiKey && + config?.polymarketClobApiSecret && + config?.polymarketClobApiPassphrase + ); +} + +function getClobAuthAddress({ config, accountAddress }) { + return ( + normalizeAddress(config?.polymarketClobAddress) ?? + normalizeAddress(accountAddress) + ); +} + +async function resolveTokenHolderAddress({ + publicClient, + config, + account, +}) { + const fallbackAddress = + getClobAuthAddress({ + config, + accountAddress: account.address, + }) ?? normalizeAddress(account.address); + + if (!config?.polymarketRelayerEnabled) { + return { + tokenHolderAddress: fallbackAddress, + tokenHolderResolutionError: null, + }; + } + + const configuredRelayerAddress = + normalizeAddress(config?.polymarketRelayerFromAddress); + if (configuredRelayerAddress) { + return { + tokenHolderAddress: configuredRelayerAddress, + tokenHolderResolutionError: null, + }; + } + + try { + const resolved = await resolveRelayerProxyWallet({ + publicClient, + account, + config, + }); + const resolvedProxyWallet = normalizeAddress(resolved?.proxyWallet); + if (!resolvedProxyWallet) { + return { + tokenHolderAddress: null, + tokenHolderResolutionError: + 'Relayer proxy wallet resolution returned an invalid address.', + }; + } + return { + tokenHolderAddress: resolvedProxyWallet, + tokenHolderResolutionError: null, + }; + } catch (error) { + return { + tokenHolderAddress: null, + tokenHolderResolutionError: error?.message ?? String(error), + }; + } +} + +function extractOrderSummary(payload) { + const order = + payload?.order && typeof payload.order === 'object' + ? payload.order + : payload && typeof payload === 'object' + ? payload + : null; + if (!order) return null; + + return { + id: normalizeOrderId(order.id ?? order.orderId ?? order.order_id), + status: normalizeClobStatus(order.status), + originalSize: parseFiniteNumber(order.original_size ?? order.originalSize), + sizeMatched: parseFiniteNumber(order.size_matched ?? order.sizeMatched), + }; +} + +function isOrderFullyMatched(order) { + if (!order) return false; + if (order.originalSize === null || order.sizeMatched === null) return false; + if (order.originalSize <= 0) return false; + return order.sizeMatched + 1e-12 >= order.originalSize; +} + +function tradeIncludesOrderId(trade, orderId) { + const normalizedOrderId = String(orderId).trim().toLowerCase(); + if (!normalizedOrderId) return false; + + const takerOrderId = normalizeOrderId(trade?.taker_order_id ?? trade?.takerOrderId); + if (takerOrderId && takerOrderId.toLowerCase() === normalizedOrderId) { + return true; + } + + const makerOrders = Array.isArray(trade?.maker_orders) + ? trade.maker_orders + : Array.isArray(trade?.makerOrders) + ? trade.makerOrders + : []; + for (const makerOrder of makerOrders) { + const makerOrderId = normalizeOrderId(makerOrder?.order_id ?? makerOrder?.orderId); + if (makerOrderId && makerOrderId.toLowerCase() === normalizedOrderId) { + return true; + } + } + + return false; +} + +function dedupeTrades(trades) { + const seen = new Set(); + const unique = []; + for (const trade of trades) { + const id = normalizeOrderId(trade?.id); + const key = id ?? JSON.stringify(trade); + if (seen.has(key)) continue; + seen.add(key); + unique.push(trade); + } + return unique; +} + +function extractOrderIdFromSubmission(parsedOutput) { + return normalizeOrderId( + parsedOutput?.result?.order?.id ?? + parsedOutput?.result?.id ?? + parsedOutput?.result?.orderID ?? + parsedOutput?.result?.orderId ?? + parsedOutput?.order?.id ?? + parsedOutput?.id ?? + parsedOutput?.orderID ?? + parsedOutput?.orderId + ); +} + +function extractOrderStatusFromSubmission(parsedOutput) { + return normalizeClobStatus( + parsedOutput?.result?.order?.status ?? + parsedOutput?.result?.status ?? + parsedOutput?.order?.status + ); +} + +async function fetchRelatedClobTrades({ + config, + signingAddress, + orderId, + market, + clobAuthAddress, + submittedMs, +}) { + const afterSeconds = Math.max(0, Math.floor((Number(submittedMs ?? Date.now()) - 60_000) / 1000)); + const all = []; + const makerTrades = await getClobTrades({ + config, + signingAddress, + maker: clobAuthAddress, + market, + after: afterSeconds, + }); + if (Array.isArray(makerTrades)) { + all.push(...makerTrades); + } + + const takerTrades = await getClobTrades({ + config, + signingAddress, + taker: clobAuthAddress, + market, + after: afterSeconds, + }); + if (Array.isArray(takerTrades)) { + all.push(...takerTrades); + } + + return dedupeTrades(all).filter((trade) => tradeIncludesOrderId(trade, orderId)); +} + +function findMatchingReimbursementProposalHash({ + signals, + policy, + proposerAddress, + recipientAddress, + reimbursementAmountWei, +}) { + const normalizedCollateralToken = normalizeAddress(policy?.collateralToken); + const normalizedProposerAddress = normalizeAddress(proposerAddress); + const normalizedRecipientAddress = normalizeAddress(recipientAddress); + const normalizedAmount = BigInt(reimbursementAmountWei ?? 0); + if ( + !normalizedCollateralToken || + !normalizedRecipientAddress || + normalizedAmount <= 0n + ) { + return null; + } + + for (const signal of signals) { + if (signal?.kind !== 'proposal') continue; + const signalHash = normalizeHash(signal.proposalHash); + if (!signalHash) continue; + + const proposer = normalizeAddress(signal.proposer); + if (normalizedProposerAddress && proposer && proposer !== normalizedProposerAddress) continue; + + const transactions = Array.isArray(signal.transactions) ? signal.transactions : []; + for (const tx of transactions) { + const txTo = normalizeAddress(tx?.to); + if (!txTo || txTo !== normalizedCollateralToken) continue; + const operation = Number(tx?.operation ?? 0); + if (operation !== 0) continue; + const value = BigInt(tx?.value ?? 0); + if (value !== 0n) continue; + const decoded = decodeErc20TransferCallData(tx?.data); + if (!decoded) continue; + if (decoded.to !== normalizedRecipientAddress) continue; + if (decoded.amount !== normalizedAmount) continue; + return signalHash; + } + } + + return null; +} + +function clearReimbursementSubmissionTracking() { + copyTradingState.reimbursementSubmissionPending = false; + copyTradingState.reimbursementSubmissionTxHash = null; + copyTradingState.reimbursementSubmissionMs = null; +} + +function resolveOgProposalHashFromToolOutput(parsedOutput) { + const txHash = normalizeHash(parsedOutput?.transactionHash); + const explicitOgHash = normalizeHash(parsedOutput?.ogProposalHash); + if (explicitOgHash) return explicitOgHash; + + const legacyHash = normalizeHash(parsedOutput?.proposalHash); + if (!legacyHash) return null; + // In legacy output shape `proposalHash` is the tx hash, not OG proposal hash. + if (txHash && legacyHash === txHash) return null; + return legacyHash; +} + +function parseActivityEntry(entry) { + if (!entry || typeof entry !== 'object') return null; + + const tradeId = + entry.id ?? + entry.tradeId ?? + entry.transactionHash ?? + entry.txHash ?? + entry.orderID ?? + entry.orderId; + const side = normalizeTradeSide(entry.side); + const outcome = normalizeOutcome(entry.outcome); + const price = normalizeTradePrice(entry.price); + + if (!tradeId || !side || !outcome || !price) return null; + + return { + id: String(tradeId), + side, + outcome, + price, + market: entry.conditionId ? String(entry.conditionId) : undefined, + timestamp: entry.timestamp ? String(entry.timestamp) : undefined, + txHash: entry.transactionHash ? String(entry.transactionHash) : undefined, + }; +} + +function getPolicy(config) { + const sourceUserRaw = process.env.COPY_TRADING_SOURCE_USER; + const market = process.env.COPY_TRADING_MARKET?.trim() || null; + const yesTokenId = normalizeTokenId(process.env.COPY_TRADING_YES_TOKEN_ID); + const noTokenId = normalizeTokenId(process.env.COPY_TRADING_NO_TOKEN_ID); + const collateralToken = + normalizeAddress(process.env.COPY_TRADING_COLLATERAL_TOKEN) ?? + normalizeAddress(DEFAULT_COLLATERAL_TOKEN); + const ctfContract = + normalizeAddress(process.env.COPY_TRADING_CTF_CONTRACT) ?? + normalizeAddress(config?.polymarketConditionalTokens); + + const errors = []; + const sourceUser = normalizeAddress(sourceUserRaw); + if (!sourceUser) errors.push('COPY_TRADING_SOURCE_USER missing or invalid address.'); + if (!market) errors.push('COPY_TRADING_MARKET is required.'); + if (!yesTokenId) errors.push('COPY_TRADING_YES_TOKEN_ID is required.'); + if (!noTokenId) errors.push('COPY_TRADING_NO_TOKEN_ID is required.'); + if (!collateralToken) { + errors.push('COPY_TRADING_COLLATERAL_TOKEN invalid and no default available.'); + } + if (!ctfContract) { + errors.push( + 'COPY_TRADING_CTF_CONTRACT invalid and POLYMARKET_CONDITIONAL_TOKENS unavailable.' + ); + } + + return { + sourceUser, + market, + yesTokenId, + noTokenId, + collateralToken, + ctfContract, + ready: errors.length === 0, + errors, + }; +} + +function calculateCopyAmounts(safeBalanceWei) { + const normalized = BigInt(safeBalanceWei ?? 0); + if (normalized <= 0n) { + return { + safeBalanceWei: '0', + copyAmountWei: '0', + feeAmountWei: '0', + }; + } + + const copyAmountWei = (normalized * COPY_BPS) / BPS_DENOMINATOR; + const feeAmountWei = normalized - copyAmountWei; + + return { + safeBalanceWei: normalized.toString(), + copyAmountWei: copyAmountWei.toString(), + feeAmountWei: feeAmountWei.toString(), + }; +} + +function computeBuyOrderAmounts({ collateralAmountWei, price }) { + const normalizedCollateralAmountWei = BigInt(collateralAmountWei); + if (normalizedCollateralAmountWei <= 0n) { + throw new Error('collateralAmountWei must be > 0 for buy-order sizing.'); + } + + const normalizedPrice = normalizeTradePrice(price); + if (!normalizedPrice) { + throw new Error('price must be a number between 0 and 1 for buy-order sizing.'); + } + + const priceScaled = BigInt(Math.round(normalizedPrice * Number(PRICE_SCALE))); + if (priceScaled <= 0n) { + throw new Error('price is too small for buy-order sizing.'); + } + + const makerAmount = (normalizedCollateralAmountWei * PRICE_SCALE) / priceScaled; + if (makerAmount <= 0n) { + throw new Error('makerAmount computed to zero; refusing order.'); + } + + return { + makerAmount: makerAmount.toString(), + takerAmount: normalizedCollateralAmountWei.toString(), + priceScaled: priceScaled.toString(), + }; +} + +async function fetchLatestSourceTrade({ policy }) { + const params = new URLSearchParams({ + user: policy.sourceUser, + limit: '10', + offset: '0', + }); + params.set('type', 'TRADE'); + params.set('market', policy.market); + + const response = await fetch(`${DATA_API_HOST}/activity?${params.toString()}`, { + signal: AbortSignal.timeout(10_000), + }); + if (!response.ok) { + throw new Error(`Data API request failed (${response.status}).`); + } + + const data = await response.json(); + if (!Array.isArray(data)) { + return null; + } + + for (const item of data) { + const parsed = parseActivityEntry(item); + if (!parsed) continue; + if (parsed.outcome !== 'YES' && parsed.outcome !== 'NO') continue; + if (parsed.side !== 'BUY') continue; + return parsed; + } + + return null; +} + +function activateTradeCandidate({ + trade, + tokenId, + copyTradeAmountWei, + reimbursementAmountWei, + reimbursementRecipientAddress, +}) { + copyTradingState.activeSourceTradeId = trade.id; + copyTradingState.activeTradeSide = trade.side; + copyTradingState.activeTradePrice = trade.price; + copyTradingState.activeOutcome = trade.outcome; + copyTradingState.activeTokenId = tokenId; + copyTradingState.copyTradeAmountWei = copyTradeAmountWei; + copyTradingState.reimbursementAmountWei = reimbursementAmountWei; + copyTradingState.reimbursementRecipientAddress = reimbursementRecipientAddress; + copyTradingState.copyOrderId = null; + copyTradingState.copyOrderStatus = null; + copyTradingState.copyOrderFilled = false; + copyTradingState.copyOrderSubmittedMs = null; + copyTradingState.orderSubmitted = false; + copyTradingState.tokenDeposited = false; + copyTradingState.reimbursementProposed = false; + copyTradingState.reimbursementProposalHash = null; + copyTradingState.reimbursementSubmissionPending = false; + copyTradingState.reimbursementSubmissionTxHash = null; + copyTradingState.reimbursementSubmissionMs = null; +} + +function clearActiveTrade({ markSeen = false } = {}) { + if (markSeen && copyTradingState.activeSourceTradeId) { + copyTradingState.seenSourceTradeId = copyTradingState.activeSourceTradeId; + } + + copyTradingState.activeSourceTradeId = null; + copyTradingState.activeTradeSide = null; + copyTradingState.activeTradePrice = null; + copyTradingState.activeOutcome = null; + copyTradingState.activeTokenId = null; + copyTradingState.copyTradeAmountWei = null; + copyTradingState.reimbursementAmountWei = null; + copyTradingState.reimbursementRecipientAddress = null; + copyTradingState.copyOrderId = null; + copyTradingState.copyOrderStatus = null; + copyTradingState.copyOrderFilled = false; + copyTradingState.copyOrderSubmittedMs = null; + copyTradingState.orderSubmitted = false; + copyTradingState.tokenDeposited = false; + copyTradingState.reimbursementProposed = false; + copyTradingState.reimbursementProposalHash = null; + copyTradingState.reimbursementSubmissionPending = false; + copyTradingState.reimbursementSubmissionTxHash = null; + copyTradingState.reimbursementSubmissionMs = null; +} + +const getPollingOptions = getAlwaysEmitBalanceSnapshotPollingOptions; + +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are a copy-trading commitment agent.', + 'Copy only BUY trades from the configured source user and configured market.', + 'Trade size must be exactly 99% of Safe collateral at detection time. Keep 1% in the Safe as fee.', + 'Flow must stay simple: place CLOB order from your configured trading wallet, wait for CLOB fill confirmation and YES/NO token receipt, deposit tokens to Safe, then propose reimbursement transfer to the same wallet that funded the copy trade.', + 'Never trade more than 99% of Safe collateral. Reimburse exactly the stored reimbursement amount (full Safe collateral at detection).', + 'Use polymarket_clob_build_sign_and_place_order for order placement, make_erc1155_deposit for YES/NO deposit, and build_og_transactions for reimbursement transfer.', + 'If preconditions are not met, return ignore.', + 'Default to disputing proposals that violate these rules; prefer no-op when unsure.', + mode, + commitmentText ? `Commitment text:\n${commitmentText}` : '', + 'If no action is needed, output strict JSON with keys: action (propose|deposit|dispute|ignore|other) and rationale (string).', + ] + .filter(Boolean) + .join(' '); +} + +async function enrichSignals(signals, { publicClient, config, account, onchainPendingProposal }) { + const policy = getPolicy(config); + const stateSnapshot = { ...copyTradingState }; + + const outSignals = [...signals]; + if (!policy.ready) { + outSignals.push({ + kind: 'copyTradingState', + policy, + state: stateSnapshot, + error: 'copy-trading policy config incomplete', + }); + return outSignals; + } + + let latestTrade = null; + let tradeFetchError; + try { + latestTrade = await fetchLatestSourceTrade({ policy }); + } catch (error) { + tradeFetchError = error?.message ?? String(error); + } + const configuredClobAddress = normalizeAddress(config?.polymarketClobAddress); + const runtimeSignerAddress = normalizeAddress(account.address); + const clobAuthAddress = configuredClobAddress ?? runtimeSignerAddress; + const clobAuthAddressLabel = configuredClobAddress + ? 'POLYMARKET_CLOB_ADDRESS' + : 'runtime signer address (fallback for POLYMARKET_CLOB_ADDRESS)'; + const { tokenHolderAddress, tokenHolderResolutionError } = await resolveTokenHolderAddress({ + publicClient, + config, + account, + }); + let walletAlignmentError = null; + if (config?.polymarketRelayerEnabled) { + if (!clobAuthAddress) { + walletAlignmentError = 'Unable to resolve CLOB auth address for relayer mode.'; + } else if (!tokenHolderAddress) { + walletAlignmentError = + tokenHolderResolutionError ?? 'Unable to resolve relayer token-holder address.'; + } else if (clobAuthAddress !== tokenHolderAddress) { + walletAlignmentError = + `${clobAuthAddressLabel} (${clobAuthAddress}) must match relayer proxy wallet (${tokenHolderAddress}) when POLYMARKET_RELAYER_ENABLED=true.`; + } + } + + const safeCollateralPromise = publicClient.readContract({ + address: policy.collateralToken, + abi: erc20Abi, + functionName: 'balanceOf', + args: [config.commitmentSafe], + }); + const yesBalancePromise = tokenHolderAddress + ? publicClient.readContract({ + address: policy.ctfContract, + abi: erc1155Abi, + functionName: 'balanceOf', + args: [tokenHolderAddress, BigInt(policy.yesTokenId)], + }) + : Promise.resolve(0n); + const noBalancePromise = tokenHolderAddress + ? publicClient.readContract({ + address: policy.ctfContract, + abi: erc1155Abi, + functionName: 'balanceOf', + args: [tokenHolderAddress, BigInt(policy.noTokenId)], + }) + : Promise.resolve(0n); + + const [safeCollateralWei, yesBalance, noBalance] = await Promise.all([ + safeCollateralPromise, + yesBalancePromise, + noBalancePromise, + ]); + + const amounts = calculateCopyAmounts(safeCollateralWei); + if ( + latestTrade && + latestTrade.side === 'BUY' && + latestTrade.id !== copyTradingState.seenSourceTradeId && + !copyTradingState.activeSourceTradeId && + !walletAlignmentError && + BigInt(amounts.copyAmountWei) > 0n + ) { + const targetTokenId = latestTrade.outcome === 'YES' ? policy.yesTokenId : policy.noTokenId; + activateTradeCandidate({ + trade: latestTrade, + tokenId: targetTokenId, + copyTradeAmountWei: amounts.copyAmountWei, + reimbursementAmountWei: amounts.safeBalanceWei, + reimbursementRecipientAddress: clobAuthAddress, + }); + } + + let orderFillCheckError; + if ( + !walletAlignmentError && + copyTradingState.activeSourceTradeId && + copyTradingState.orderSubmitted && + !copyTradingState.tokenDeposited && + !copyTradingState.copyOrderFilled && + copyTradingState.copyOrderId + ) { + if (hasClobCredentials(config) && clobAuthAddress) { + try { + const signingAddress = clobAuthAddress; + const orderPayload = await getClobOrder({ + config, + signingAddress, + orderId: copyTradingState.copyOrderId, + }); + const orderSummary = extractOrderSummary(orderPayload); + if (orderSummary?.status) { + copyTradingState.copyOrderStatus = orderSummary.status; + } + + const relatedTrades = await fetchRelatedClobTrades({ + config, + signingAddress, + orderId: copyTradingState.copyOrderId, + market: policy.market, + clobAuthAddress, + submittedMs: copyTradingState.copyOrderSubmittedMs, + }); + const relatedStatuses = relatedTrades + .map((trade) => normalizeClobStatus(trade?.status)) + .filter(Boolean); + const anyFailedTrade = relatedStatuses.some( + (status) => status === CLOB_FAILURE_TERMINAL_STATUS + ); + const allConfirmedTrades = + relatedStatuses.length > 0 && + relatedStatuses.every((status) => status === CLOB_SUCCESS_TERMINAL_STATUS); + const orderFilled = + isOrderFullyMatched(orderSummary) || + CLOB_ORDER_FILLED_STATUSES.has(orderSummary?.status ?? ''); + const orderFailed = CLOB_ORDER_FAILURE_STATUSES.has(orderSummary?.status ?? ''); + + if (orderFailed || anyFailedTrade) { + copyTradingState.orderSubmitted = false; + copyTradingState.copyOrderFilled = false; + copyTradingState.copyOrderId = null; + copyTradingState.copyOrderSubmittedMs = null; + } else if (allConfirmedTrades && orderFilled) { + copyTradingState.copyOrderFilled = true; + } + } catch (error) { + orderFillCheckError = error?.message ?? String(error); + } + } + } + + if ( + copyTradingState.reimbursementSubmissionPending && + !copyTradingState.reimbursementProposalHash + ) { + const recoveredHash = findMatchingReimbursementProposalHash({ + signals: outSignals, + policy, + proposerAddress: account.address, + recipientAddress: + normalizeAddress(copyTradingState.reimbursementRecipientAddress) ?? + clobAuthAddress ?? + normalizeAddress(account.address), + reimbursementAmountWei: copyTradingState.reimbursementAmountWei, + }); + if (recoveredHash) { + copyTradingState.reimbursementProposalHash = recoveredHash; + copyTradingState.reimbursementProposed = true; + clearReimbursementSubmissionTracking(); + } else { + const submissionTxHash = normalizeHash(copyTradingState.reimbursementSubmissionTxHash); + const submissionMs = Number(copyTradingState.reimbursementSubmissionMs ?? 0); + const submissionExpired = + Number.isFinite(submissionMs) && + submissionMs > 0 && + Date.now() - submissionMs > REIMBURSEMENT_SUBMISSION_TIMEOUT_MS; + + if (submissionTxHash) { + try { + const receipt = await publicClient.getTransactionReceipt({ + hash: submissionTxHash, + }); + const status = receipt?.status; + const reverted = status === 0n || status === 0 || status === 'reverted'; + if (reverted || (submissionExpired && !onchainPendingProposal)) { + clearReimbursementSubmissionTracking(); + } + } catch (error) { + if (submissionExpired && !onchainPendingProposal) { + clearReimbursementSubmissionTracking(); + } + } + } else if (submissionExpired && !onchainPendingProposal) { + clearReimbursementSubmissionTracking(); + } + } + } + + const activeTokenBalance = + copyTradingState.activeTokenId === policy.yesTokenId + ? yesBalance + : copyTradingState.activeTokenId === policy.noTokenId + ? noBalance + : 0n; + + outSignals.push({ + kind: 'copyTradingState', + policy, + state: { ...copyTradingState }, + latestObservedTrade: latestTrade, + balances: { + safeCollateralWei: safeCollateralWei.toString(), + yesBalance: yesBalance.toString(), + noBalance: noBalance.toString(), + activeTokenBalance: activeTokenBalance.toString(), + tokenHolderAddress, + }, + metrics: { + ...amounts, + copyBps: COPY_BPS.toString(), + feeBps: FEE_BPS.toString(), + }, + pendingProposal: Boolean( + onchainPendingProposal || + copyTradingState.reimbursementProposed || + copyTradingState.reimbursementSubmissionPending + ), + tradeFetchError, + orderFillCheckError, + tokenHolderResolutionError, + walletAlignmentError, + }); + + return outSignals; +} + +function findCopySignal(signals) { + return signals.find((signal) => signal?.kind === 'copyTradingState'); +} + +async function validateToolCalls({ + toolCalls, + signals, + config, + agentAddress, + onchainPendingProposal, +}) { + const copySignal = findCopySignal(signals ?? []); + if (!copySignal || !copySignal.policy?.ready) { + return []; + } + + const validated = []; + const policy = copySignal.policy; + const state = copySignal.state ?? {}; + const activeTokenBalance = BigInt(copySignal.balances?.activeTokenBalance ?? 0); + const pendingProposal = Boolean(onchainPendingProposal || copySignal.pendingProposal); + const walletAlignmentError = copySignal.walletAlignmentError; + if (walletAlignmentError) { + const disputeCalls = toolCalls.filter((call) => call?.name === 'dispute_assertion'); + if (disputeCalls.length > 0) { + return disputeCalls; + } + throw new Error(walletAlignmentError); + } + + for (const call of toolCalls) { + if (call.name === 'dispute_assertion') { + validated.push(call); + continue; + } + + if (call.name === 'post_bond_and_propose') { + continue; + } + + if (call.name === 'polymarket_clob_build_sign_and_place_order') { + if (!state.activeSourceTradeId) { + throw new Error('No active source trade to copy.'); + } + if (state.orderSubmitted) { + throw new Error('Copy order already submitted for active trade.'); + } + if (state.activeTradeSide !== 'BUY') { + throw new Error('Only BUY source trades are eligible for copy trading.'); + } + if (state.activeTradePrice === null || state.activeTradePrice === undefined) { + throw new Error('Missing triggering trade price snapshot for active trade.'); + } + if (!state.activeTokenId) { + throw new Error('No active YES/NO token id configured for copy trade.'); + } + const copyTradeAmountWei = BigInt(state.copyTradeAmountWei ?? 0); + if (copyTradeAmountWei <= 0n) { + throw new Error('Copy-trade amount is zero; refusing copy-trade order.'); + } + + const { makerAmount, takerAmount } = computeBuyOrderAmounts({ + collateralAmountWei: copyTradeAmountWei, + price: state.activeTradePrice, + }); + + validated.push({ + ...call, + parsedArguments: { + side: 'BUY', + tokenId: String(state.activeTokenId), + orderType: 'FOK', + makerAmount, + takerAmount, + }, + }); + continue; + } + + if (call.name === 'make_erc1155_deposit') { + if (!state.orderSubmitted) { + throw new Error('Cannot deposit YES/NO tokens before copy order submission.'); + } + if (!state.copyOrderFilled) { + throw new Error('Copy order has not been filled yet; wait before depositing tokens.'); + } + if (state.tokenDeposited) { + throw new Error('YES/NO tokens already deposited for active trade.'); + } + if (!state.activeTokenId) { + throw new Error('No active YES/NO token id for deposit.'); + } + if (activeTokenBalance <= 0n) { + throw new Error('No YES/NO token balance available to deposit yet.'); + } + + validated.push({ + ...call, + parsedArguments: { + token: policy.ctfContract, + tokenId: String(state.activeTokenId), + amount: activeTokenBalance.toString(), + data: '0x', + }, + }); + continue; + } + + if (call.name === 'build_og_transactions') { + if (!state.tokenDeposited) { + throw new Error('Cannot build reimbursement proposal before token deposit confirmation.'); + } + if (state.reimbursementProposed || state.reimbursementSubmissionPending) { + throw new Error('Reimbursement proposal already submitted for active trade.'); + } + if (pendingProposal) { + throw new Error('Pending proposal exists; wait before proposing reimbursement.'); + } + const reimbursementAmountWei = BigInt(state.reimbursementAmountWei ?? 0); + if (reimbursementAmountWei <= 0n) { + throw new Error('Reimbursement amount is zero; refusing proposal build.'); + } + const reimbursementRecipientAddress = + normalizeAddress(state.reimbursementRecipientAddress) ?? + normalizeAddress(agentAddress); + if (!reimbursementRecipientAddress) { + throw new Error('Missing reimbursement recipient address for proposal build.'); + } + + validated.push({ + ...call, + parsedArguments: { + actions: [ + { + kind: 'erc20_transfer', + token: policy.collateralToken, + to: reimbursementRecipientAddress, + amountWei: reimbursementAmountWei.toString(), + }, + ], + }, + }); + continue; + } + + // Ignore all other tool calls for this specialized module. + } + + return validated; +} + +function onToolOutput({ name, parsedOutput }) { + if (!name || !parsedOutput || parsedOutput.status === 'error') { + return; + } + + if (name === 'polymarket_clob_build_sign_and_place_order' && parsedOutput.status === 'submitted') { + const submittedOrderId = extractOrderIdFromSubmission(parsedOutput); + copyTradingState.orderSubmitted = Boolean(submittedOrderId); + copyTradingState.copyOrderId = submittedOrderId; + copyTradingState.copyOrderStatus = extractOrderStatusFromSubmission(parsedOutput); + copyTradingState.copyOrderFilled = false; + copyTradingState.copyOrderSubmittedMs = submittedOrderId ? Date.now() : null; + return; + } + + if (name === 'make_erc1155_deposit' && parsedOutput.status === 'confirmed') { + copyTradingState.tokenDeposited = true; + return; + } + + if ( + (name === 'post_bond_and_propose' || name === 'auto_post_bond_and_propose') && + parsedOutput.status === 'submitted' + ) { + const proposalHash = resolveOgProposalHashFromToolOutput(parsedOutput); + const txHash = normalizeHash(parsedOutput.transactionHash); + if (proposalHash) { + copyTradingState.reimbursementProposed = true; + copyTradingState.reimbursementProposalHash = proposalHash; + copyTradingState.reimbursementSubmissionPending = false; + copyTradingState.reimbursementSubmissionTxHash = txHash; + copyTradingState.reimbursementSubmissionMs = null; + } else if (txHash) { + copyTradingState.reimbursementProposed = false; + copyTradingState.reimbursementProposalHash = null; + copyTradingState.reimbursementSubmissionPending = true; + copyTradingState.reimbursementSubmissionTxHash = txHash; + copyTradingState.reimbursementSubmissionMs = Date.now(); + } else { + copyTradingState.reimbursementProposed = false; + copyTradingState.reimbursementProposalHash = null; + clearReimbursementSubmissionTracking(); + } + } +} + +function onProposalEvents({ + executedProposals = [], + deletedProposals = [], + executedProposalCount = 0, + deletedProposalCount = 0, +}) { + const trackedHash = normalizeHash(copyTradingState.reimbursementProposalHash); + const executedHashes = Array.isArray(executedProposals) + ? executedProposals.map((hash) => normalizeHash(hash)).filter(Boolean) + : []; + const deletedHashes = Array.isArray(deletedProposals) + ? deletedProposals.map((hash) => normalizeHash(hash)).filter(Boolean) + : []; + + if (trackedHash && executedHashes.includes(trackedHash)) { + clearActiveTrade({ markSeen: true }); + } + + if (trackedHash && deletedHashes.includes(trackedHash)) { + copyTradingState.reimbursementProposed = false; + copyTradingState.reimbursementProposalHash = null; + clearReimbursementSubmissionTracking(); + } + + // Backward-compatible fallback for environments that only pass counts and no hashes. + if ( + !trackedHash && + copyTradingState.reimbursementProposed && + executedProposalCount > 0 && + (!Array.isArray(executedProposals) || executedProposals.length === 0) + ) { + clearActiveTrade({ markSeen: true }); + } + if ( + !trackedHash && + copyTradingState.reimbursementProposed && + deletedProposalCount > 0 && + (!Array.isArray(deletedProposals) || deletedProposals.length === 0) + ) { + copyTradingState.reimbursementProposed = false; + clearReimbursementSubmissionTracking(); + } +} + +function getCopyTradingState() { + return { ...copyTradingState }; +} + +function resetCopyTradingState() { + copyTradingState = { + seenSourceTradeId: null, + activeSourceTradeId: null, + activeTradeSide: null, + activeTradePrice: null, + activeOutcome: null, + activeTokenId: null, + copyTradeAmountWei: null, + reimbursementAmountWei: null, + reimbursementRecipientAddress: null, + copyOrderId: null, + copyOrderStatus: null, + copyOrderFilled: false, + copyOrderSubmittedMs: null, + orderSubmitted: false, + tokenDeposited: false, + reimbursementProposed: false, + reimbursementProposalHash: null, + reimbursementSubmissionPending: false, + reimbursementSubmissionTxHash: null, + reimbursementSubmissionMs: null, + }; +} + +export { + calculateCopyAmounts, + computeBuyOrderAmounts, + enrichSignals, + getCopyTradingState, + getPollingOptions, + getSystemPrompt, + onProposalEvents, + onToolOutput, + resetCopyTradingState, + validateToolCalls, +}; diff --git a/agent-library/agents/copy-trading/agent.json b/agent-library/agents/copy-trading/agent.json new file mode 100644 index 00000000..ddb004a0 --- /dev/null +++ b/agent-library/agents/copy-trading/agent.json @@ -0,0 +1,13 @@ +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "Oya Copy Trading Agent", + "description": "Copy-trading commitment agent that mirrors configured Polymarket BUY trades at 99% Safe sizing, deposits YES/NO tokens, and proposes reimbursement.", + "image": "https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/copy-trading/agent.png", + "endpoints": [ + { + "name": "agentWallet", + "endpoint": "eip155:137:0x0000000000000000000000000000000000000000" + } + ], + "registrations": [] +} diff --git a/agent-library/agents/copy-trading/commitment.txt b/agent-library/agents/copy-trading/commitment.txt new file mode 100644 index 00000000..22d78dd2 --- /dev/null +++ b/agent-library/agents/copy-trading/commitment.txt @@ -0,0 +1,13 @@ +This commitment accepts deposits from multiple users. + +The agent monitors a single configured source trader and a single configured Polymarket market. +When the source trader executes a BUY trade in that market, the agent may copy that trade direction. + +The agent must size the copied trade to exactly 99% of the Safe's collateral balance at detection time. +The remaining 1% stays in the Safe as the agent fee. + +The agent executes the copied trade from the configured copy-trading funding wallet through the Polymarket CLOB API. +After receiving YES or NO tokens, the agent deposits those ERC1155 tokens into this Safe. + +After token deposit confirmation, the agent proposes one reimbursement transfer from this Safe to that same copy-trading funding wallet equal to the full Safe collateral balance captured when the copy trade was detected. +No other transfers are allowed. diff --git a/agent-library/agents/copy-trading/test-copy-trading-agent.mjs b/agent-library/agents/copy-trading/test-copy-trading-agent.mjs new file mode 100644 index 00000000..7005337b --- /dev/null +++ b/agent-library/agents/copy-trading/test-copy-trading-agent.mjs @@ -0,0 +1,1519 @@ +import assert from 'node:assert/strict'; +import { + calculateCopyAmounts, + computeBuyOrderAmounts, + enrichSignals, + getCopyTradingState, + getSystemPrompt, + onProposalEvents, + onToolOutput, + resetCopyTradingState, + validateToolCalls, +} from './agent.js'; + +const YES_TOKEN_ID = '123'; +const NO_TOKEN_ID = '456'; +const TEST_ACCOUNT = '0x1111111111111111111111111111111111111111'; +const TEST_SAFE = '0x2222222222222222222222222222222222222222'; +const TEST_SOURCE_USER = '0x3333333333333333333333333333333333333333'; +const TEST_CLOB_PROXY = '0x4444444444444444444444444444444444444444'; +const TEST_RELAYER_PROXY = '0x5555555555555555555555555555555555555555'; +const TEST_PROPOSAL_HASH = `0x${'a'.repeat(64)}`; +const OTHER_PROPOSAL_HASH = `0x${'b'.repeat(64)}`; +const TEST_TX_HASH = `0x${'c'.repeat(64)}`; + +function encodeErc20TransferData({ to, amount }) { + const toWord = to.toLowerCase().replace(/^0x/, '').padStart(64, '0'); + const amountWord = BigInt(amount).toString(16).padStart(64, '0'); + return `0xa9059cbb${toWord}${amountWord}`; +} + +function runPromptTest() { + const prompt = getSystemPrompt({ + proposeEnabled: true, + disputeEnabled: true, + commitmentText: 'Copy-trade commitment.', + }); + + assert.ok(prompt.includes('copy-trading commitment agent')); + assert.ok(prompt.includes('99%')); + assert.ok(prompt.includes('1%')); +} + +function runMathTests() { + const amounts = calculateCopyAmounts(1_000_000n); + assert.equal(amounts.copyAmountWei, '990000'); + assert.equal(amounts.feeAmountWei, '10000'); + + const sized = computeBuyOrderAmounts({ + collateralAmountWei: 990000n, + price: 0.55, + }); + assert.equal(sized.takerAmount, '990000'); + assert.ok(BigInt(sized.makerAmount) > 0n); +} + +async function runValidateToolCallTests() { + const policy = { + ready: true, + ctfContract: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045', + collateralToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + }; + + const orderValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'order', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy, + state: { + activeSourceTradeId: 'trade-1', + activeTradeSide: 'BUY', + activeTradePrice: 0.55, + activeTokenId: '123', + copyTradeAmountWei: '990000', + reimbursementAmountWei: '1000000', + orderSubmitted: false, + tokenDeposited: false, + reimbursementProposed: false, + }, + latestObservedTrade: { + side: 'SELL', + price: 0.99, + }, + balances: { + activeTokenBalance: '0', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: '0x1111111111111111111111111111111111111111', + onchainPendingProposal: false, + }); + assert.equal(orderValidated.length, 1); + assert.equal(orderValidated[0].parsedArguments.side, 'BUY'); + assert.equal(orderValidated[0].parsedArguments.tokenId, '123'); + assert.equal(orderValidated[0].parsedArguments.orderType, 'FOK'); + assert.equal(orderValidated[0].parsedArguments.makerAmount, '1800000'); + assert.equal(orderValidated[0].parsedArguments.takerAmount, '990000'); + + const depositValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'deposit', + name: 'make_erc1155_deposit', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy, + state: { + activeSourceTradeId: 'trade-1', + activeTradeSide: 'BUY', + activeTradePrice: 0.55, + activeTokenId: '123', + copyTradeAmountWei: '990000', + reimbursementAmountWei: '1000000', + copyOrderId: 'order-1', + copyOrderFilled: true, + orderSubmitted: true, + tokenDeposited: false, + reimbursementProposed: false, + }, + balances: { + activeTokenBalance: '5', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: '0x1111111111111111111111111111111111111111', + onchainPendingProposal: false, + }); + assert.equal(depositValidated.length, 1); + assert.equal(depositValidated[0].parsedArguments.token, policy.ctfContract); + assert.equal(depositValidated[0].parsedArguments.tokenId, '123'); + assert.equal(depositValidated[0].parsedArguments.amount, '5'); + + await assert.rejects( + () => + validateToolCalls({ + toolCalls: [ + { + callId: 'deposit-not-filled', + name: 'make_erc1155_deposit', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy, + state: { + activeSourceTradeId: 'trade-1', + activeTradeSide: 'BUY', + activeTradePrice: 0.55, + activeTokenId: '123', + copyTradeAmountWei: '990000', + reimbursementAmountWei: '1000000', + copyOrderId: 'order-1', + copyOrderFilled: false, + orderSubmitted: true, + tokenDeposited: false, + reimbursementProposed: false, + }, + balances: { + activeTokenBalance: '5', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: '0x1111111111111111111111111111111111111111', + onchainPendingProposal: false, + }), + /not been filled yet/ + ); + + const reimbursementValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'reimbursement', + name: 'build_og_transactions', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy, + state: { + activeSourceTradeId: 'trade-1', + activeTradeSide: 'BUY', + activeTradePrice: 0.55, + activeTokenId: '123', + copyTradeAmountWei: '990000', + reimbursementAmountWei: '1000000', + reimbursementRecipientAddress: TEST_CLOB_PROXY, + orderSubmitted: true, + tokenDeposited: true, + reimbursementProposed: false, + }, + balances: { + activeTokenBalance: '0', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: '0x1111111111111111111111111111111111111111', + onchainPendingProposal: false, + }); + assert.equal(reimbursementValidated.length, 1); + assert.equal(reimbursementValidated[0].parsedArguments.actions.length, 1); + assert.equal(reimbursementValidated[0].parsedArguments.actions[0].kind, 'erc20_transfer'); + assert.equal( + reimbursementValidated[0].parsedArguments.actions[0].to.toLowerCase(), + TEST_CLOB_PROXY.toLowerCase() + ); + assert.equal(reimbursementValidated[0].parsedArguments.actions[0].amountWei, '1000000'); +} + +async function runProposalHashGatingTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + await enrichSignals([], { + publicClient: { + async readContract({ args }) { + if (args.length === 1) { + return 1_000_000n; + } + return 0n; + }, + }, + config: { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + }, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + let state = getCopyTradingState(); + assert.equal(state.activeSourceTradeId, 'trade-1'); + assert.equal(state.activeTradeSide, 'BUY'); + assert.equal(state.activeTradePrice, 0.5); + assert.equal(state.copyTradeAmountWei, '990000'); + assert.equal(state.reimbursementAmountWei, '1000000'); + assert.equal(state.reimbursementRecipientAddress, TEST_ACCOUNT.toLowerCase()); + + onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { + status: 'submitted', + transactionHash: TEST_TX_HASH, + proposalHash: TEST_TX_HASH, + ogProposalHash: TEST_PROPOSAL_HASH, + }, + }); + + state = getCopyTradingState(); + assert.equal(state.reimbursementProposed, true); + assert.equal(state.reimbursementProposalHash, TEST_PROPOSAL_HASH); + + onProposalEvents({ + executedProposals: [OTHER_PROPOSAL_HASH], + executedProposalCount: 1, + }); + state = getCopyTradingState(); + assert.equal(state.activeSourceTradeId, 'trade-1'); + + onProposalEvents({ + executedProposals: [TEST_PROPOSAL_HASH], + executedProposalCount: 1, + }); + state = getCopyTradingState(); + assert.equal(state.activeSourceTradeId, null); + assert.equal(state.seenSourceTradeId, 'trade-1'); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +function runLegacyProposalHashFallbackTest() { + resetCopyTradingState(); + onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { status: 'submitted', proposalHash: TEST_PROPOSAL_HASH }, + }); + const state = getCopyTradingState(); + assert.equal(state.reimbursementProposed, true); + assert.equal(state.reimbursementProposalHash, TEST_PROPOSAL_HASH); + assert.equal(state.reimbursementSubmissionPending, false); + resetCopyTradingState(); +} + +async function runProposalHashRecoveryFromSignalTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const config = { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + }; + const publicClient = { + async readContract({ args }) { + if (args.length === 1) return 1_000_000n; + return 0n; + }, + }; + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { status: 'submitted', transactionHash: TEST_TX_HASH }, + }); + + let state = getCopyTradingState(); + assert.equal(state.reimbursementProposed, false); + assert.equal(state.reimbursementProposalHash, null); + assert.equal(state.reimbursementSubmissionPending, true); + assert.equal(state.reimbursementSubmissionTxHash, TEST_TX_HASH); + assert.equal(typeof state.reimbursementSubmissionMs, 'number'); + assert.equal(state.copyTradeAmountWei, '990000'); + assert.equal(state.reimbursementAmountWei, '1000000'); + + const reimbursementAmountWei = state.reimbursementAmountWei; + const proposalSignal = { + kind: 'proposal', + proposalHash: TEST_PROPOSAL_HASH, + proposer: TEST_ACCOUNT, + transactions: [ + { + to: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + value: 0n, + operation: 0, + data: encodeErc20TransferData({ + to: TEST_ACCOUNT, + amount: reimbursementAmountWei, + }), + }, + ], + }; + + await enrichSignals([proposalSignal], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: true, + }); + + state = getCopyTradingState(); + assert.equal(state.reimbursementProposed, true); + assert.equal(state.reimbursementProposalHash, TEST_PROPOSAL_HASH); + assert.equal(state.reimbursementSubmissionPending, false); + + onProposalEvents({ + executedProposals: [TEST_PROPOSAL_HASH], + executedProposalCount: 1, + }); + state = getCopyTradingState(); + assert.equal(state.activeSourceTradeId, null); + assert.equal(state.seenSourceTradeId, 'trade-1'); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runProposalHashRecoveryFromSignalUsesFundingWalletTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const config = { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + polymarketClobAddress: TEST_CLOB_PROXY, + }; + const publicClient = { + async readContract({ args }) { + if (args.length === 1) return 1_000_000n; + return 0n; + }, + }; + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { status: 'submitted', transactionHash: TEST_TX_HASH }, + }); + + let state = getCopyTradingState(); + assert.equal(state.reimbursementRecipientAddress, TEST_CLOB_PROXY.toLowerCase()); + assert.equal(state.reimbursementSubmissionPending, true); + + const reimbursementAmountWei = state.reimbursementAmountWei; + const wrongRecipientSignal = { + kind: 'proposal', + proposalHash: OTHER_PROPOSAL_HASH, + proposer: TEST_ACCOUNT, + transactions: [ + { + to: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + value: 0n, + operation: 0, + data: encodeErc20TransferData({ + to: TEST_ACCOUNT, + amount: reimbursementAmountWei, + }), + }, + ], + }; + + await enrichSignals([wrongRecipientSignal], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: true, + }); + + state = getCopyTradingState(); + assert.equal(state.reimbursementProposalHash, null); + assert.equal(state.reimbursementSubmissionPending, true); + + const correctRecipientSignal = { + kind: 'proposal', + proposalHash: TEST_PROPOSAL_HASH, + proposer: TEST_ACCOUNT, + transactions: [ + { + to: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + value: 0n, + operation: 0, + data: encodeErc20TransferData({ + to: TEST_CLOB_PROXY, + amount: reimbursementAmountWei, + }), + }, + ], + }; + + await enrichSignals([correctRecipientSignal], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: true, + }); + + state = getCopyTradingState(); + assert.equal(state.reimbursementProposed, true); + assert.equal(state.reimbursementProposalHash, TEST_PROPOSAL_HASH); + assert.equal(state.reimbursementSubmissionPending, false); + + onProposalEvents({ + executedProposals: [TEST_PROPOSAL_HASH], + executedProposalCount: 1, + }); + state = getCopyTradingState(); + assert.equal(state.activeSourceTradeId, null); + assert.equal(state.seenSourceTradeId, 'trade-1'); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runRevertedSubmissionClearsPendingTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const config = { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + }; + const publicClient = { + async readContract({ args }) { + if (args.length === 1) return 1_000_000n; + return 0n; + }, + async getTransactionReceipt() { + return { status: 0n }; + }, + }; + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + onToolOutput({ + name: 'polymarket_clob_build_sign_and_place_order', + parsedOutput: { status: 'submitted' }, + }); + onToolOutput({ + name: 'make_erc1155_deposit', + parsedOutput: { status: 'confirmed' }, + }); + onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { status: 'submitted', transactionHash: TEST_TX_HASH }, + }); + + let state = getCopyTradingState(); + assert.equal(state.reimbursementSubmissionPending, true); + assert.equal(state.reimbursementSubmissionTxHash, TEST_TX_HASH); + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + state = getCopyTradingState(); + assert.equal(state.reimbursementSubmissionPending, false); + assert.equal(state.reimbursementSubmissionTxHash, null); + assert.equal(state.reimbursementSubmissionMs, null); + + const reimbursementValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'reimbursement', + name: 'build_og_transactions', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy: { + ready: true, + ctfContract: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045', + collateralToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + }, + state, + balances: { + activeTokenBalance: '0', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }); + + assert.equal(reimbursementValidated.length, 1); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runOrderFillConfirmationGatesDepositTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + const apiSecret = Buffer.from('test-secret').toString('base64'); + globalThis.fetch = async (url) => { + const asText = String(url); + if (asText.includes('data-api.polymarket.com/activity')) { + return { + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + async text() { + return JSON.stringify([ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]); + }, + }; + } + if (asText.includes('/data/order/order-1')) { + return { + ok: true, + async text() { + return JSON.stringify({ + order: { + id: 'order-1', + status: 'filled', + original_size: '100', + size_matched: '100', + }, + }); + }, + }; + } + if (asText.includes('/data/trades?')) { + return { + ok: true, + async text() { + return JSON.stringify([ + { + id: 'trade-confirmed-1', + status: 'CONFIRMED', + taker_order_id: 'order-1', + }, + ]); + }, + }; + } + throw new Error(`Unexpected fetch URL in test: ${asText}`); + }; + + const config = { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + polymarketClobHost: 'https://clob.polymarket.com', + polymarketClobApiKey: 'api-key', + polymarketClobApiSecret: apiSecret, + polymarketClobApiPassphrase: 'pass', + }; + const publicClient = { + async readContract({ args }) { + if (args.length === 1) return 1_000_000n; + return 5n; + }, + }; + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + onToolOutput({ + name: 'polymarket_clob_build_sign_and_place_order', + parsedOutput: { + status: 'submitted', + result: { + id: 'order-1', + status: 'live', + }, + }, + }); + + let state = getCopyTradingState(); + assert.equal(state.copyOrderId, 'order-1'); + assert.equal(state.copyOrderFilled, false); + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + state = getCopyTradingState(); + assert.equal(state.copyOrderId, 'order-1'); + assert.equal(state.copyOrderFilled, true); + assert.equal(state.copyOrderStatus, 'FILLED'); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runMissingOrderIdDoesNotAdvanceOrderStateTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const config = { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + }; + const publicClient = { + async readContract({ args }) { + if (args.length === 1) return 1_000_000n; + return 5n; + }, + }; + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + onToolOutput({ + name: 'polymarket_clob_build_sign_and_place_order', + parsedOutput: { status: 'submitted' }, + }); + + const state = getCopyTradingState(); + assert.equal(state.orderSubmitted, false); + assert.equal(state.copyOrderId, null); + assert.equal(state.copyOrderSubmittedMs, null); + assert.equal(state.copyOrderFilled, false); + + await assert.rejects( + () => + validateToolCalls({ + toolCalls: [ + { + callId: 'deposit-no-order-id', + name: 'make_erc1155_deposit', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy: { + ready: true, + ctfContract: config.polymarketConditionalTokens, + collateralToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + }, + state, + balances: { + activeTokenBalance: '5', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }), + /before copy order submission/ + ); + + const orderValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'order-retry', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy: { + ready: true, + ctfContract: config.polymarketConditionalTokens, + collateralToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + }, + state, + balances: { + activeTokenBalance: '5', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }); + assert.equal(orderValidated.length, 1); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runSubmissionWithoutHashesDoesNotWedgeTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const config = { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + }; + const publicClient = { + async readContract({ args }) { + if (args.length === 1) return 1_000_000n; + return 0n; + }, + }; + + await enrichSignals([], { + publicClient, + config, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + onToolOutput({ + name: 'polymarket_clob_build_sign_and_place_order', + parsedOutput: { status: 'submitted' }, + }); + onToolOutput({ + name: 'make_erc1155_deposit', + parsedOutput: { status: 'confirmed' }, + }); + onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { status: 'submitted' }, + }); + + const state = getCopyTradingState(); + assert.equal(state.reimbursementProposed, false); + assert.equal(state.reimbursementProposalHash, null); + assert.equal(state.reimbursementSubmissionPending, false); + assert.equal(state.reimbursementSubmissionTxHash, null); + + const reimbursementValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'reimbursement', + name: 'build_og_transactions', + arguments: {}, + }, + ], + signals: [ + { + kind: 'copyTradingState', + policy: { + ready: true, + ctfContract: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045', + collateralToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + }, + state, + balances: { + activeTokenBalance: '0', + }, + pendingProposal: false, + }, + ], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }); + + assert.equal(reimbursementValidated.length, 1); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runFetchLatestBuyTradeTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-sell', + side: 'SELL', + outcome: 'YES', + price: 0.51, + }, + { + id: 'trade-buy', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const outSignals = await enrichSignals([], { + publicClient: { + async readContract({ args }) { + if (args.length === 1) { + return 1_000_000n; + } + return 0n; + }, + }, + config: { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + }, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + const state = getCopyTradingState(); + assert.equal(state.activeSourceTradeId, 'trade-buy'); + assert.equal(state.activeTradeSide, 'BUY'); + + const copySignal = outSignals.find((signal) => signal.kind === 'copyTradingState'); + assert.equal(copySignal.latestObservedTrade.id, 'trade-buy'); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runTokenBalancesUseClobAddressTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + globalThis.fetch = async () => ({ + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + }); + + const erc1155BalanceCallAddresses = []; + await enrichSignals([], { + publicClient: { + async readContract({ args }) { + if (args.length === 1) { + return 1_000_000n; + } + erc1155BalanceCallAddresses.push(String(args[0]).toLowerCase()); + return 1n; + }, + }, + config: { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + polymarketClobAddress: TEST_CLOB_PROXY, + }, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + assert.equal(erc1155BalanceCallAddresses.length, 2); + assert.equal(erc1155BalanceCallAddresses[0], TEST_CLOB_PROXY.toLowerCase()); + assert.equal(erc1155BalanceCallAddresses[1], TEST_CLOB_PROXY.toLowerCase()); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runTokenBalancesUseResolvedRelayerProxyTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + const builderSecret = Buffer.from('test-builder-secret').toString('base64'); + globalThis.fetch = async (url) => { + const asText = String(url); + if (asText.includes('data-api.polymarket.com/activity')) { + return { + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + async text() { + return JSON.stringify([ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]); + }, + }; + } + if (asText.includes('/relay-payload?')) { + return { + ok: true, + async text() { + return JSON.stringify({ + address: TEST_RELAYER_PROXY, + }); + }, + }; + } + throw new Error(`Unexpected fetch URL in relayer test: ${asText}`); + }; + + const erc1155BalanceCallAddresses = []; + const outSignals = await enrichSignals([], { + publicClient: { + async getChainId() { + return 137; + }, + async readContract({ args }) { + if (args.length === 1) { + return 1_000_000n; + } + erc1155BalanceCallAddresses.push(String(args[0]).toLowerCase()); + return 1n; + }, + }, + config: { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'SAFE', + polymarketClobAddress: TEST_RELAYER_PROXY, + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: builderSecret, + polymarketBuilderPassphrase: 'builder-passphrase', + }, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + assert.equal(erc1155BalanceCallAddresses.length, 2); + assert.equal(erc1155BalanceCallAddresses[0], TEST_RELAYER_PROXY.toLowerCase()); + assert.equal(erc1155BalanceCallAddresses[1], TEST_RELAYER_PROXY.toLowerCase()); + const copySignal = outSignals.find((signal) => signal.kind === 'copyTradingState'); + assert.equal(copySignal.balances.tokenHolderAddress, TEST_RELAYER_PROXY.toLowerCase()); + assert.equal(copySignal.tokenHolderResolutionError, null); + assert.equal(copySignal.walletAlignmentError, null); + assert.equal(copySignal.state.reimbursementRecipientAddress, TEST_RELAYER_PROXY.toLowerCase()); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function runRelayerWalletMismatchIsBlockedTest() { + resetCopyTradingState(); + const envKeys = [ + 'COPY_TRADING_SOURCE_USER', + 'COPY_TRADING_MARKET', + 'COPY_TRADING_YES_TOKEN_ID', + 'COPY_TRADING_NO_TOKEN_ID', + ]; + const oldEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])); + const oldFetch = globalThis.fetch; + + process.env.COPY_TRADING_SOURCE_USER = TEST_SOURCE_USER; + process.env.COPY_TRADING_MARKET = 'test-market'; + process.env.COPY_TRADING_YES_TOKEN_ID = YES_TOKEN_ID; + process.env.COPY_TRADING_NO_TOKEN_ID = NO_TOKEN_ID; + + try { + const builderSecret = Buffer.from('test-builder-secret').toString('base64'); + globalThis.fetch = async (url) => { + const asText = String(url); + if (asText.includes('data-api.polymarket.com/activity')) { + return { + ok: true, + async json() { + return [ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]; + }, + async text() { + return JSON.stringify([ + { + id: 'trade-1', + side: 'BUY', + outcome: 'YES', + price: 0.5, + }, + ]); + }, + }; + } + if (asText.includes('/relay-payload?')) { + return { + ok: true, + async text() { + return JSON.stringify({ + address: TEST_RELAYER_PROXY, + }); + }, + }; + } + throw new Error(`Unexpected fetch URL in relayer mismatch test: ${asText}`); + }; + + const outSignals = await enrichSignals([], { + publicClient: { + async getChainId() { + return 137; + }, + async readContract({ args }) { + if (args.length === 1) { + return 1_000_000n; + } + return 1n; + }, + }, + config: { + commitmentSafe: TEST_SAFE, + polymarketConditionalTokens: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'SAFE', + polymarketRelayerFromAddress: TEST_RELAYER_PROXY, + polymarketClobAddress: TEST_CLOB_PROXY, + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: builderSecret, + polymarketBuilderPassphrase: 'builder-passphrase', + }, + account: { address: TEST_ACCOUNT }, + onchainPendingProposal: false, + }); + + const copySignal = outSignals.find((signal) => signal.kind === 'copyTradingState'); + assert.ok(copySignal.walletAlignmentError.includes('POLYMARKET_CLOB_ADDRESS')); + assert.equal(copySignal.state.activeSourceTradeId, null); + + await assert.rejects( + () => + validateToolCalls({ + toolCalls: [ + { + callId: 'order', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: {}, + }, + ], + signals: [copySignal], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }), + /must match relayer proxy wallet/ + ); + + const disputeValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'dispute', + name: 'dispute_assertion', + arguments: {}, + }, + ], + signals: [copySignal], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }); + assert.equal(disputeValidated.length, 1); + assert.equal(disputeValidated[0].name, 'dispute_assertion'); + + const mixedValidated = await validateToolCalls({ + toolCalls: [ + { + callId: 'order', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: {}, + }, + { + callId: 'dispute', + name: 'dispute_assertion', + arguments: {}, + }, + ], + signals: [copySignal], + config: {}, + agentAddress: TEST_ACCOUNT, + onchainPendingProposal: false, + }); + assert.equal(mixedValidated.length, 1); + assert.equal(mixedValidated[0].name, 'dispute_assertion'); + } finally { + for (const key of envKeys) { + if (oldEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = oldEnv[key]; + } + } + globalThis.fetch = oldFetch; + resetCopyTradingState(); + } +} + +async function run() { + runPromptTest(); + runMathTests(); + runLegacyProposalHashFallbackTest(); + await runValidateToolCallTests(); + await runProposalHashGatingTest(); + await runProposalHashRecoveryFromSignalTest(); + await runProposalHashRecoveryFromSignalUsesFundingWalletTest(); + await runRevertedSubmissionClearsPendingTest(); + await runOrderFillConfirmationGatesDepositTest(); + await runMissingOrderIdDoesNotAdvanceOrderStateTest(); + await runSubmissionWithoutHashesDoesNotWedgeTest(); + await runFetchLatestBuyTradeTest(); + await runTokenBalancesUseClobAddressTest(); + await runTokenBalancesUseResolvedRelayerProxyTest(); + await runRelayerWalletMismatchIsBlockedTest(); + console.log('[test] copy-trading agent OK'); +} + +run(); diff --git a/agent-library/agents/dca-agent/agent.js b/agent-library/agents/dca-agent/agent.js new file mode 100644 index 00000000..eff2a2e8 --- /dev/null +++ b/agent-library/agents/dca-agent/agent.js @@ -0,0 +1,267 @@ +// DCA Agent - WETH reimbursement loop on Sepolia + +import { erc20Abi, parseAbi } from 'viem'; + +let lastDcaTimestamp = Date.now(); +const DCA_INTERVAL_SECONDS = 200; +const MAX_CYCLES = 2; +const CHAINLINK_ETH_USD_FEED_SEPOLIA = '0x694AA1769357215DE4FAC081bf1f309aDC325306'; +const chainlinkAbi = parseAbi([ + 'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', +]); +const DCA_POLICY = Object.freeze({ + wethAddress: '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', + usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', + usdcDecimals: 6n, + minSafeUsdcWei: 100000n, // 0.10 USDC (6 decimals) + maxCycles: MAX_CYCLES, + proposalConfirmTimeoutMs: 60000, +}); +let dcaState = { + depositConfirmed: false, + proposalBuilt: false, + proposalPosted: false, + cyclesCompleted: 0, + proposalSubmitHash: null, + proposalSubmitMs: null, +}; + +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are a DCA (Dollar Cost Averaging) service agent.', + `Every ${DCA_INTERVAL_SECONDS} seconds, you deliver $0.10 worth of WETH to the Safe and get reimbursed in USDC.`, + 'Stop after 2 cycles (MAX_CYCLES = 2). If signals.dcaState.cyclesCompleted >= 2, output action=ignore and do nothing.', + `Flow: 1) Read balances from signals (Safe USDC and Self WETH), 2) If time >= ${DCA_INTERVAL_SECONDS}s and balances ok, send WETH, 3) Propose USDC reimbursement to OG.`, + `Check timeSinceLastDca in signals. If >= ${DCA_INTERVAL_SECONDS} seconds and balances from signals are sufficient, proceed.`, + 'Current ETH/WETH price is provided in signals as ethPriceUSD (from Chainlink oracle).', + 'Calculate: wethToSend = 0.10 / ethPriceUSD, then convert to wei (18 decimals).', + 'Example: if ETH is $2242.51, then 0.10 / 2242.51 = 0.0000446... WETH = 44600000000000 wei.', + 'First, read Safe USDC and Self WETH balances from signals.balances (note: 100000 micro-USDC = 0.10 USDC).', + 'Second, read signals.dcaState to see which steps already completed: depositConfirmed, proposalBuilt, proposalPosted, cyclesCompleted.', + 'If signals.pendingProposal is true, output action=ignore and do not call post_bond_and_propose until it becomes false.', + `Third, if timeSinceLastDca >= ${DCA_INTERVAL_SECONDS} seconds and balances are sufficient and depositConfirmed=false, perform a single chained action in ONE response: (a) make_deposit with asset=WETH_ADDRESS and amountWei=calculated amount (waits for confirmation), then (b) build_og_transactions for one erc20_transfer of 100000 micro-USDC to agentAddress, then (c) post_bond_and_propose with those transactions.`, + 'Fourth, if depositConfirmed=true and proposalBuilt=false, call build_og_transactions and post_bond_and_propose in the same response.', + 'Fifth, if proposalBuilt=true and proposalPosted=false, call post_bond_and_propose.', + 'Do NOT repeat make_deposit when depositConfirmed=true; use dcaState to avoid duplicate deposits.', + 'If any precondition fails (balances insufficient, time not met, or Safe lacks USDC), output action=ignore.', + 'Use signals.balances.safeUsdcSufficient (boolean) or compare safeUsdcWei against minSafeUsdcWei to decide if Safe has enough USDC.', + 'All non-tool responses must be valid json.', + mode, + commitmentText ? `\nCommitment:\n${commitmentText}` : '', + 'Always verify Safe has at least 100000 micro-USDC (0.10 USDC) before proposing reimbursement.', + 'The agentAddress is provided in the input signals.', + 'Use Sepolia USDC token address 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 for reimbursement transfers (do NOT use mainnet USDC).', + 'WETH address on Sepolia: 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', + ] + .filter(Boolean) + .join(' '); +} + +function augmentSignals(signals) { + const now = Date.now(); + const timeSinceLastDca = Math.floor((now - lastDcaTimestamp) / 1000); + + return [ + ...signals, + { + kind: 'timer', + timeSinceLastDca, + shouldExecuteDca: timeSinceLastDca >= DCA_INTERVAL_SECONDS, + lastDcaTimestamp, + currentTimestamp: now, + }, + ]; +} + +function markDcaExecuted() { + lastDcaTimestamp = Date.now(); +} + +async function getEthPriceUSD(publicClient, chainlinkFeedAddress) { + const result = await publicClient.readContract({ + address: chainlinkFeedAddress, + abi: chainlinkAbi, + functionName: 'latestRoundData', + }); + const answer = result[1]; + if (answer <= 0n) { + throw new Error('Invalid Chainlink ETH/USD price'); + } + return Number(answer) / 1e8; +} + +async function getEthPriceUSDFallback() { + const response = await fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd' + ); + if (!response.ok) { + throw new Error(`Coingecko API error: ${response.status}`); + } + const data = await response.json(); + if (!data?.ethereum?.usd) { + throw new Error('Invalid Coingecko response'); + } + return data.ethereum.usd; +} + +async function enrichSignals(signals, { publicClient, config, account, onchainPendingProposal }) { + if (!signals.some((signal) => signal.kind === 'timer')) { + return signals; + } + + let ethPriceUSD; + try { + const chainlinkFeedAddress = + config?.chainlinkPriceFeed ?? CHAINLINK_ETH_USD_FEED_SEPOLIA; + ethPriceUSD = await getEthPriceUSD(publicClient, chainlinkFeedAddress); + } catch (error) { + ethPriceUSD = await getEthPriceUSDFallback(); + } + + const [safeUsdcWei, selfWethWei] = await Promise.all([ + publicClient.readContract({ + address: DCA_POLICY.usdcAddress, + abi: erc20Abi, + functionName: 'balanceOf', + args: [config.commitmentSafe], + }), + publicClient.readContract({ + address: DCA_POLICY.wethAddress, + abi: erc20Abi, + functionName: 'balanceOf', + args: [account.address], + }), + ]); + + const safeUsdcSufficient = safeUsdcWei >= DCA_POLICY.minSafeUsdcWei; + const safeUsdcHuman = Number(safeUsdcWei) / 10 ** Number(DCA_POLICY.usdcDecimals); + const selfWethHuman = Number(selfWethWei) / 1e18; + const pendingProposal = getPendingProposal(onchainPendingProposal); + const currentDcaState = getDcaState(); + + return signals.map((signal) => { + if (signal.kind !== 'timer') return signal; + return { + ...signal, + ethPriceUSD, + balances: { + safeUsdcWei: safeUsdcWei.toString(), + selfWethWei: selfWethWei.toString(), + safeUsdcHuman, + selfWethHuman, + safeUsdcSufficient, + minSafeUsdcWei: DCA_POLICY.minSafeUsdcWei.toString(), + minSafeUsdcHuman: + Number(DCA_POLICY.minSafeUsdcWei) / 10 ** Number(DCA_POLICY.usdcDecimals), + }, + dcaState: currentDcaState, + pendingProposal, + }; + }); +} + +function getDcaPolicy() { + return DCA_POLICY; +} + +function getDcaState() { + return { ...dcaState }; +} + +function getPendingProposal(onchainPending) { + return Boolean(onchainPending || dcaState.proposalPosted); +} + +function onToolOutput({ name, parsedOutput }) { + if (!name || !parsedOutput || parsedOutput.status === 'error') return; + + if (name === 'make_deposit' && parsedOutput.status === 'confirmed') { + dcaState.depositConfirmed = true; + dcaState.proposalBuilt = false; + dcaState.proposalPosted = false; + return; + } + + if (name === 'build_og_transactions' && parsedOutput.status === 'ok') { + dcaState.proposalBuilt = true; + return; + } + + if (name === 'post_bond_and_propose' && parsedOutput.status === 'submitted') { + dcaState.proposalPosted = true; + dcaState.depositConfirmed = false; + dcaState.proposalBuilt = false; + dcaState.proposalSubmitHash = + parsedOutput.transactionHash ?? parsedOutput.proposalHash ?? null; + dcaState.proposalSubmitMs = Date.now(); + } +} + +function onProposalEvents({ executedProposalCount = 0, deletedProposalCount = 0 }) { + if (executedProposalCount > 0) { + dcaState.proposalPosted = false; + dcaState.proposalBuilt = false; + dcaState.depositConfirmed = false; + dcaState.proposalSubmitHash = null; + dcaState.proposalSubmitMs = null; + dcaState.cyclesCompleted = Math.min( + DCA_POLICY.maxCycles, + dcaState.cyclesCompleted + executedProposalCount + ); + markDcaExecuted(); + } + + if (deletedProposalCount > 0) { + dcaState.proposalPosted = false; + dcaState.proposalBuilt = false; + dcaState.depositConfirmed = false; + dcaState.proposalSubmitHash = null; + dcaState.proposalSubmitMs = null; + } +} + +async function reconcileProposalSubmission({ publicClient }) { + if (!dcaState.proposalPosted || !dcaState.proposalSubmitHash || !dcaState.proposalSubmitMs) { + return; + } + + try { + const receipt = await publicClient.getTransactionReceipt({ + hash: dcaState.proposalSubmitHash, + }); + if (receipt?.status === 0n || receipt?.status === 'reverted') { + dcaState.proposalPosted = false; + dcaState.proposalBuilt = false; + dcaState.proposalSubmitHash = null; + dcaState.proposalSubmitMs = null; + } + } catch (error) { + if (Date.now() - dcaState.proposalSubmitMs > DCA_POLICY.proposalConfirmTimeoutMs) { + dcaState.proposalPosted = false; + dcaState.proposalBuilt = false; + dcaState.proposalSubmitHash = null; + dcaState.proposalSubmitMs = null; + } + } +} + +export { + getSystemPrompt, + augmentSignals, + markDcaExecuted, + getDcaPolicy, + getDcaState, + getPendingProposal, + onToolOutput, + onProposalEvents, + reconcileProposalSubmission, + enrichSignals, +}; diff --git a/agent-library/agents/dca-agent/agent.json b/agent-library/agents/dca-agent/agent.json new file mode 100644 index 00000000..666fdc33 --- /dev/null +++ b/agent-library/agents/dca-agent/agent.json @@ -0,0 +1,18 @@ +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "Oya DCA Agent", + "description": "DCA reimbursement agent for commitments: sends $0.10 WETH to the Safe every 45 seconds and proposes $0.10 USDC reimbursement.", + "image": "https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/dca-agent/agent.png", + "endpoints": [ + { + "name": "agentWallet", + "endpoint": "eip155:11155111:0xff0a50c38b6dd8b1d3181bfec33b64a6d9467319" + } + ], + "registrations": [ + { + "agentId": "1139", + "agentRegistry": "eip155:11155111:0x8004a818bfb912233c491871b3d84c89a494bd9e" + } + ] +} diff --git a/agent-library/agents/dca-agent/commitment.txt b/agent-library/agents/dca-agent/commitment.txt new file mode 100644 index 00000000..a0db3485 --- /dev/null +++ b/agent-library/agents/dca-agent/commitment.txt @@ -0,0 +1,6 @@ +I would like to DCA (Dollar Cost Average) into Ethereum by purchasing a fixed USD amount of WETH periodically. +Every 45 seconds, an agent may send $0.10 worth of WETH to this Safe to receive reimbursement of exactly $0.10 USDC. +The agent should use the latest Chainlink ETH/USD price at the time of proposal to determine the amount of WETH to transfer. +The Safe will transfer $0.10 USDC to the agent's address if the proposal is valid. +No other transactions are permitted. +If the Safe has insufficient USDC balance, the agent should not propose. diff --git a/agent-library/agents/default/agent.js b/agent-library/agents/default/agent.js new file mode 100644 index 00000000..377b37aa --- /dev/null +++ b/agent-library/agents/default/agent.js @@ -0,0 +1,26 @@ +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are an agent monitoring an onchain commitment (Safe + Optimistic Governor).', + 'Your own address is provided in the input as agentAddress; use it when rules refer to “the agent/themselves”.', + 'Given signals and rules, recommend a course of action.', + 'Default to disputing proposals that violate the rules; prefer no-op when unsure.', + mode, + commitmentText ? `Commitment text:\n${commitmentText}` : '', + 'If an onchain action is needed, call a tool.', + 'Use build_og_transactions to construct proposal payloads, then post_bond_and_propose.', + 'Use dispute_assertion with a short human-readable explanation when disputing.', + 'If no action is needed, output strict JSON with keys: action (propose|deposit|dispute|ignore|other) and rationale (string).', + ] + .filter(Boolean) + .join(' '); +} + +export { getSystemPrompt }; diff --git a/agent-library/agents/default/agent.json b/agent-library/agents/default/agent.json new file mode 100644 index 00000000..8011a85d --- /dev/null +++ b/agent-library/agents/default/agent.json @@ -0,0 +1,18 @@ +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "Oya Default Agent", + "description": "Default Oya commitment agent that monitors deposits and proposals, and can propose or dispute actions based on the commitment rules.", + "image": "https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/default/agent.png", + "endpoints": [ + { + "name": "agentWallet", + "endpoint": "eip155:11155111:0x2967c076182f0303037072670e744e26ed4a830f" + } + ], + "registrations": [ + { + "agentId": 899, + "agentRegistry": "eip155:11155111:0x8004a818bfb912233c491871b3d84c89a494bd9e" + } + ] +} diff --git a/agent-library/agents/default/commitment.txt b/agent-library/agents/default/commitment.txt new file mode 100644 index 00000000..064ae23f --- /dev/null +++ b/agent-library/agents/default/commitment.txt @@ -0,0 +1 @@ +The agent watching this commitment should send any funds deposited into this commitment to themselves. \ No newline at end of file diff --git a/agent-library/agents/default/test-default-agent.mjs b/agent-library/agents/default/test-default-agent.mjs new file mode 100644 index 00000000..e483f2f6 --- /dev/null +++ b/agent-library/agents/default/test-default-agent.mjs @@ -0,0 +1,16 @@ +import assert from 'node:assert/strict'; +import { getSystemPrompt } from './agent.js'; + +function run() { + const prompt = getSystemPrompt({ + proposeEnabled: true, + disputeEnabled: true, + commitmentText: 'Test commitment.', + }); + + assert.ok(prompt.includes('monitoring an onchain commitment')); + assert.ok(prompt.includes('Commitment text')); + console.log('[test] default agent prompt OK'); +} + +run(); diff --git a/agent-library/agents/limit-order-sma/agent.js b/agent-library/agents/limit-order-sma/agent.js new file mode 100644 index 00000000..88f814ef --- /dev/null +++ b/agent-library/agents/limit-order-sma/agent.js @@ -0,0 +1,522 @@ +// SMA Limit Order Agent - Single limit order with 200-day SMA as dynamic limit on Sepolia (WETH/USDC) + +import { erc20Abi, parseAbiItem } from 'viem'; +import { normalizeAddressOrThrow } from '../../../agent/src/lib/utils.js'; + +const TOKENS = Object.freeze({ + WETH: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + USDC: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238', +}); +const DEFAULT_ROUTER = '0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e'; +const ALLOWED_ROUTERS = new Set([DEFAULT_ROUTER]); +const ALLOWED_FEE_TIERS = new Set([500, 3000, 10000]); +const QUOTER_CANDIDATES_BY_CHAIN = new Map([ + [1, ['0x61fFE014bA17989E743c5F6cB21bF9697530B21e', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6']], + [11155111, ['0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6']], +]); +const SLIPPAGE_BPS = 50; +const SMA_CACHE_TTL_MS = 12 * 60 * 60 * 1000; // 12 hours +const SMA_MIN_POINTS = 100; +const COINGECKO_MARKET_CHART_URL = + 'https://api.coingecko.com/api/v3/coins/ethereum/market_chart?vs_currency=usd&days=200'; + +const quoterV2Abi = [ + { + type: 'function', + name: 'quoteExactInputSingle', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'params', + type: 'tuple', + components: [ + { name: 'tokenIn', type: 'address' }, + { name: 'tokenOut', type: 'address' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'fee', type: 'uint24' }, + { name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + }, + ], + outputs: [ + { name: 'amountOut', type: 'uint256' }, + { name: 'sqrtPriceX96After', type: 'uint160' }, + { name: 'initializedTicksCrossed', type: 'uint32' }, + { name: 'gasEstimate', type: 'uint256' }, + ], + }, +]; +const quoterV1Abi = [ + { + type: 'function', + name: 'quoteExactInputSingle', + stateMutability: 'view', + inputs: [ + { name: 'tokenIn', type: 'address' }, + { name: 'tokenOut', type: 'address' }, + { name: 'fee', type: 'uint24' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + outputs: [{ name: 'amountOut', type: 'uint256' }], + }, +]; + +let lastPollTimestamp = Date.now(); +let limitOrderState = { + proposalBuilt: false, + proposalPosted: false, + orderFilled: false, + proposalSubmitHash: null, + proposalSubmitMs: null, +}; +let hydratedFromChain = false; +let priceDataCache = { ethPriceUSD: null, smaEth200USD: null, fetchedAt: 0 }; +const normalizeAddress = (value) => normalizeAddressOrThrow(value, { requireHex: false }); + +const proposalExecutedEvent = parseAbiItem( + 'event ProposalExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId)' +); + +async function fetchEthPriceDataFromCoinGecko() { + const now = Date.now(); + if ( + priceDataCache.ethPriceUSD !== null && + priceDataCache.smaEth200USD !== null && + now - priceDataCache.fetchedAt < SMA_CACHE_TTL_MS + ) { + return { + ethPriceUSD: priceDataCache.ethPriceUSD, + smaEth200USD: priceDataCache.smaEth200USD, + fetchedAt: priceDataCache.fetchedAt, + }; + } + + const apiKey = process.env.COINGECKO_API_KEY; + const baseUrl = apiKey + ? 'https://pro-api.coingecko.com/api/v3/coins/ethereum/market_chart?vs_currency=usd&days=200' + : COINGECKO_MARKET_CHART_URL; + const headers = apiKey ? { 'x-cg-pro-api-key': apiKey } : {}; + + const response = await fetch(baseUrl, { headers }); + if (!response.ok) { + throw new Error(`CoinGecko market_chart API error: ${response.status}`); + } + const data = await response.json(); + const prices = data?.prices; + if (!Array.isArray(prices) || prices.length < SMA_MIN_POINTS) { + throw new Error( + `Insufficient price data: got ${prices?.length ?? 0} points, need at least ${SMA_MIN_POINTS}` + ); + } + + const priceValues = prices.map((p) => (Array.isArray(p) ? p[1] : 0)).filter((v) => typeof v === 'number' && v > 0); + if (priceValues.length < SMA_MIN_POINTS) { + throw new Error( + `Insufficient valid price points: got ${priceValues.length}, need at least ${SMA_MIN_POINTS}` + ); + } + + const sum = priceValues.reduce((a, b) => a + b, 0); + const smaEth200USD = sum / priceValues.length; + const ethPriceUSD = priceValues[priceValues.length - 1]; + + priceDataCache = { ethPriceUSD, smaEth200USD, fetchedAt: now }; + return { ethPriceUSD, smaEth200USD, fetchedAt: now }; +} + +async function resolveQuoterCandidates({ publicClient, config }) { + if (config?.uniswapV3Quoter) { + return [normalizeAddress(String(config.uniswapV3Quoter))]; + } + const chainId = await publicClient.getChainId(); + const byChain = QUOTER_CANDIDATES_BY_CHAIN.get(Number(chainId)); + if (!Array.isArray(byChain) || byChain.length === 0) { + throw new Error(`No Uniswap V3 quoter configured for chainId ${chainId}. Set UNISWAP_V3_QUOTER.`); + } + return byChain.map((v) => normalizeAddress(v)); +} + +async function tryQuoteV2({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }) { + const quoteCall = await publicClient.simulateContract({ + address: quoter, + abi: quoterV2Abi, + functionName: 'quoteExactInputSingle', + args: [{ tokenIn, tokenOut, fee, amountIn, sqrtPriceLimitX96: 0n }], + }); + const result = quoteCall?.result; + return Array.isArray(result) && result.length > 0 ? BigInt(result[0]) : BigInt(result ?? 0n); +} + +async function tryQuoteV1({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }) { + const quoteCall = await publicClient.simulateContract({ + address: quoter, + abi: quoterV1Abi, + functionName: 'quoteExactInputSingle', + args: [tokenIn, tokenOut, fee, amountIn, 0n], + }); + return BigInt(quoteCall?.result ?? 0n); +} + +async function quoteMinOutWithSlippage({ publicClient, config, tokenIn, tokenOut, fee, amountIn }) { + const quoters = await resolveQuoterCandidates({ publicClient, config }); + let quotedAmountOut = 0n; + let selectedQuoter = null; + const failures = []; + + for (const quoter of quoters) { + try { + quotedAmountOut = await tryQuoteV2({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }); + selectedQuoter = quoter; + break; + } catch (v2Error) { + try { + quotedAmountOut = await tryQuoteV1({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }); + selectedQuoter = quoter; + break; + } catch (v1Error) { + failures.push( + `${quoter}: ${v1Error?.message ?? v2Error?.message ?? 'quote failed'}` + ); + } + } + } + + if (!selectedQuoter) { + throw new Error(`No compatible Uniswap quoter found. Tried: ${failures.join(' | ')}`); + } + if (quotedAmountOut <= 0n) { + throw new Error('Uniswap quoter returned zero output for this swap.'); + } + const minAmountOut = (quotedAmountOut * BigInt(10_000 - SLIPPAGE_BPS)) / 10_000n; + if (minAmountOut <= 0n) { + throw new Error('Swap output too small after slippage; refusing proposal.'); + } + return { minAmountOut }; +} + +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are a limit order agent with a dynamic limit price from the 200-day Simple Moving Average (SMA).', + 'The limit price is smaEth200USD (from signals), not a static value. Compare ethPriceUSD to smaEth200USD. Both come from CoinGecko 200-day market data.', + 'When ethPriceUSD <= smaEth200USD and smaEth200USD is truthy, propose a single swap of the Safe\'s funds.', + 'If smaEth200USD is null or missing, output action=ignore.', + 'No deposits. The Safe must be pre-funded. Recipient of the swap is always the Safe (commitmentSafe).', + 'Read signals: ethPriceUSD (current price), smaEth200USD (200-day SMA), safeWethHuman, safeUsdcHuman (human-readable balances), limitOrderState, pendingProposal.', + 'If the price condition is met and orderFilled is false and Safe has sufficient balance and no pendingProposal, call build_og_transactions with one uniswap_v3_exact_input_single action, then post_bond_and_propose.', + 'Extract tokenIn, tokenOut, amountInWei from the commitment. Set recipient to commitmentSafe. Use router at 0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e and an allowlisted fee tier (500, 3000, 10000).', + 'If the price condition is not met, or ethPriceUSD or smaEth200USD is null, or orderFilled is true, or pendingProposal, or insufficient balance, output action=ignore.', + 'Single execution only. Never propose make_deposit; reject any such tool call.', + mode, + commitmentText ? `\nCommitment:\n${commitmentText}` : '', + 'If no action is needed, output strict JSON with keys: action (propose|dispute|ignore|other) and rationale (string).', + ] + .filter(Boolean) + .join(' '); +} + +function augmentSignals(signals) { + const now = Date.now(); + lastPollTimestamp = now; + return [ + ...signals, + { + kind: 'priceSignal', + currentTimestamp: now, + lastPollTimestamp: now, + }, + ]; +} + +async function enrichSignals(signals, { publicClient, config, account, onchainPendingProposal }) { + if (!signals.some((s) => s.kind === 'priceSignal')) { + return signals; + } + + let ethPriceUSD = null; + let smaEth200USD = null; + let smaEth200USDAt = null; + try { + const result = await fetchEthPriceDataFromCoinGecko(); + ethPriceUSD = result.ethPriceUSD; + smaEth200USD = result.smaEth200USD; + smaEth200USDAt = result.fetchedAt; + } catch { + ethPriceUSD = null; + smaEth200USD = null; + smaEth200USDAt = null; + } + + const [safeWethWei, safeUsdcWei] = await Promise.all([ + publicClient.readContract({ + address: TOKENS.WETH, + abi: erc20Abi, + functionName: 'balanceOf', + args: [config.commitmentSafe], + }), + publicClient.readContract({ + address: TOKENS.USDC, + abi: erc20Abi, + functionName: 'balanceOf', + args: [config.commitmentSafe], + }), + ]); + + const safeWethHuman = Number(safeWethWei) / 1e18; + const safeUsdcHuman = Number(safeUsdcWei) / 1e6; + const pendingProposal = Boolean(onchainPendingProposal || limitOrderState.proposalPosted); + + return signals.map((signal) => { + if (signal.kind !== 'priceSignal') return signal; + return { + ...signal, + ethPriceUSD, + smaEth200USD, + smaEth200USDAt, + safeWethHuman, + safeUsdcHuman, + limitOrderState: { ...limitOrderState }, + pendingProposal, + }; + }); +} + +function parseCallArgs(call) { + if (call?.parsedArguments && typeof call.parsedArguments === 'object') { + return call.parsedArguments; + } + if (typeof call?.arguments === 'string') { + try { + return JSON.parse(call.arguments); + } catch { + return null; + } + } + return null; +} + +async function validateToolCalls({ + toolCalls, + signals, + commitmentText, + commitmentSafe, + publicClient, + config, + onchainPendingProposal, +}) { + const validated = []; + const safeAddress = commitmentSafe ? String(commitmentSafe).toLowerCase() : null; + + for (const call of toolCalls) { + if (call.name === 'dispute_assertion') { + validated.push(call); + continue; + } + if (call.name === 'make_deposit') { + throw new Error('Limit order agent does not use make_deposit; reject.'); + } + if (call.name === 'post_bond_and_propose') { + continue; + } + if (call.name !== 'build_og_transactions') { + continue; + } + + if (onchainPendingProposal) { + throw new Error('Pending proposal exists onchain; execute it before creating a new proposal.'); + } + if (limitOrderState.orderFilled) { + throw new Error('Limit order already filled; single-fire lock.'); + } + if (limitOrderState.proposalPosted) { + throw new Error('Proposal already submitted; wait for execution.'); + } + + const args = parseCallArgs(call); + if (!args || !Array.isArray(args.actions) || args.actions.length !== 1) { + throw new Error('build_og_transactions must include exactly one swap action.'); + } + + const action = args.actions[0]; + if (action.kind !== 'uniswap_v3_exact_input_single') { + throw new Error('Only uniswap_v3_exact_input_single is allowed.'); + } + + const tokenIn = normalizeAddress(String(action.tokenIn)); + const tokenOut = normalizeAddress(String(action.tokenOut)); + const recipient = normalizeAddress(String(action.recipient ?? safeAddress)); + const router = normalizeAddress(String(action.router ?? DEFAULT_ROUTER)); + const fee = Number(action.fee ?? 3000); + const amountInWei = BigInt(String(action.amountInWei)); + + if (!action.tokenIn || !action.tokenOut || !action.amountInWei) { + throw new Error('action must include tokenIn, tokenOut, and amountInWei.'); + } + if (tokenIn !== TOKENS.WETH && tokenIn !== TOKENS.USDC) { + throw new Error('tokenIn must be Sepolia WETH or USDC.'); + } + if (tokenOut !== TOKENS.WETH && tokenOut !== TOKENS.USDC) { + throw new Error('tokenOut must be Sepolia WETH or USDC.'); + } + if (tokenIn === tokenOut) { + throw new Error('tokenIn and tokenOut must differ.'); + } + if (recipient !== safeAddress) { + throw new Error('Recipient must be the commitment Safe.'); + } + if (!ALLOWED_ROUTERS.has(router)) { + throw new Error(`Router ${router} is not allowlisted.`); + } + if (!ALLOWED_FEE_TIERS.has(fee)) { + throw new Error(`Fee tier ${fee} is not allowlisted.`); + } + if (amountInWei <= 0n) { + throw new Error('amountInWei must be positive.'); + } + + const inputBalance = await publicClient.readContract({ + address: tokenIn, + abi: erc20Abi, + functionName: 'balanceOf', + args: [commitmentSafe], + }); + if (BigInt(inputBalance) < amountInWei) { + throw new Error('Safe has insufficient balance for this swap.'); + } + + const { minAmountOut } = await quoteMinOutWithSlippage({ + publicClient, + config, + tokenIn, + tokenOut, + fee, + amountIn: amountInWei, + }); + + action.tokenIn = tokenIn; + action.tokenOut = tokenOut; + action.router = router; + action.recipient = safeAddress; + action.operation = 0; + action.fee = fee; + action.amountInWei = amountInWei.toString(); + action.amountOutMinWei = minAmountOut.toString(); + args.actions[0] = action; + + const { ethPriceUSD, smaEth200USD } = await fetchEthPriceDataFromCoinGecko(); + if ( + smaEth200USD == null || + !Number.isFinite(smaEth200USD) || + !Number.isFinite(ethPriceUSD) + ) { + throw new Error('SMA or price data unavailable; cannot validate condition.'); + } + if (ethPriceUSD > smaEth200USD) { + throw new Error( + `SMA condition not met: ethPriceUSD ${ethPriceUSD} > smaEth200USD ${smaEth200USD}` + ); + } + + validated.push({ ...call, parsedArguments: args }); + } + + return validated; +} + +function onToolOutput({ name, parsedOutput }) { + if (!name || !parsedOutput || parsedOutput.status === 'error') return; + + if (name === 'build_og_transactions' && parsedOutput.status === 'ok') { + limitOrderState.proposalBuilt = true; + return; + } + + if (name === 'post_bond_and_propose' && parsedOutput.status === 'submitted') { + const submitHash = parsedOutput.transactionHash ?? parsedOutput.proposalHash ?? null; + if (submitHash) { + limitOrderState.proposalPosted = true; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = submitHash; + limitOrderState.proposalSubmitMs = Date.now(); + } + } +} + +function onProposalEvents({ executedProposalCount = 0, deletedProposalCount = 0 }) { + if (executedProposalCount > 0) { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + limitOrderState.orderFilled = true; + } + if (deletedProposalCount > 0) { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } +} + +async function reconcileProposalSubmission({ publicClient, ogModule, startBlock }) { + if (!hydratedFromChain && ogModule) { + try { + const toBlock = await publicClient.getBlockNumber(); + const fromBlock = startBlock ?? 0n; + const logs = await publicClient.getLogs({ + address: ogModule, + event: proposalExecutedEvent, + fromBlock, + toBlock, + }); + if (logs.length > 0) { + limitOrderState.orderFilled = true; + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } + hydratedFromChain = true; + } catch (err) { + console.warn('[limit-order-sma] Failed to hydrate from chain:', err?.message ?? err); + } + } + if (!limitOrderState.proposalPosted || !limitOrderState.proposalSubmitHash) return; + try { + const receipt = await publicClient.getTransactionReceipt({ + hash: limitOrderState.proposalSubmitHash, + }); + if (receipt?.status === 0n || receipt?.status === 'reverted') { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } + } catch { + if (Date.now() - (limitOrderState.proposalSubmitMs ?? 0) > 60_000) { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } + } +} + +export { + getSystemPrompt, + augmentSignals, + enrichSignals, + validateToolCalls, + onToolOutput, + onProposalEvents, + reconcileProposalSubmission, + fetchEthPriceDataFromCoinGecko, +}; diff --git a/agent-library/agents/limit-order-sma/agent.json b/agent-library/agents/limit-order-sma/agent.json new file mode 100644 index 00000000..96af9717 --- /dev/null +++ b/agent-library/agents/limit-order-sma/agent.json @@ -0,0 +1 @@ +{"type":"https://eips.ethereum.org/EIPS/eip-8004#registration-v1","name":"Oya SMA Limit Order Agent","description":"Single limit order with dynamic limit from 200-day SMA: when ethPriceUSD <= smaEth200USD, proposes uniswap_v3_exact_input_single so the Safe swaps its pre-funded WETH or USDC. Recipient = Safe.","image":"https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/default/agent.png","endpoints":[{"name":"agentWallet","endpoint":"eip155:11155111:0x0000000000000000000000000000000000000000"}],"registrations":[]} diff --git a/agent-library/agents/limit-order-sma/commitment.txt b/agent-library/agents/limit-order-sma/commitment.txt new file mode 100644 index 00000000..0e34818e --- /dev/null +++ b/agent-library/agents/limit-order-sma/commitment.txt @@ -0,0 +1,6 @@ +Limit Order Rules (SMA-based): Purchase WETH when ETH price is at or below the 200-day Simple Moving Average. + +When ethPriceUSD (CoinGecko) is less than or equal to smaEth200USD (200-day SMA from CoinGecko), propose a swap of 1 USDC ($1 = 1000000 micro-USDC) for WETH using this Safe's funds. +The Safe must be pre-funded with sufficient USDC. Swap output (WETH) goes to this Safe. +Token addresses: WETH 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9, USDC 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238. +Only execute one swap. Max slippage 0.50%. diff --git a/agent-library/agents/limit-order-sma/simulate-sma-limit-order.mjs b/agent-library/agents/limit-order-sma/simulate-sma-limit-order.mjs new file mode 100644 index 00000000..f44afdbf --- /dev/null +++ b/agent-library/agents/limit-order-sma/simulate-sma-limit-order.mjs @@ -0,0 +1,46 @@ +/** + * Simulates when an SMA-based limit order would trigger. + * lte: trigger when ethPrice <= sma (buy when price dips below average) + * gte: trigger when ethPrice >= sma (sell when price rises above average) + */ +function wouldTrigger({ ethPrice, sma, comparator }) { + if (comparator === 'lte') return ethPrice <= sma; + if (comparator === 'gte') return ethPrice >= sma; + throw new Error(`Unknown comparator: ${comparator}`); +} + +function run() { + const scenarios = [ + { + name: 'lte: price below SMA -> trigger', + input: { ethPrice: 1999, sma: 2000, comparator: 'lte' }, + }, + { + name: 'lte: price at SMA -> trigger', + input: { ethPrice: 2000, sma: 2000, comparator: 'lte' }, + }, + { + name: 'lte: price above SMA -> no trigger', + input: { ethPrice: 2001, sma: 2000, comparator: 'lte' }, + }, + { + name: 'gte: price above SMA -> trigger', + input: { ethPrice: 2500, sma: 2000, comparator: 'gte' }, + }, + { + name: 'gte: price at SMA -> trigger', + input: { ethPrice: 2000, sma: 2000, comparator: 'gte' }, + }, + { + name: 'gte: price below SMA -> no trigger', + input: { ethPrice: 1500, sma: 2000, comparator: 'gte' }, + }, + ]; + + for (const scenario of scenarios) { + const result = wouldTrigger(scenario.input); + console.log(`[sim] ${scenario.name}:`, result); + } +} + +run(); diff --git a/agent-library/agents/limit-order-sma/test-limit-order-sma-agent.mjs b/agent-library/agents/limit-order-sma/test-limit-order-sma-agent.mjs new file mode 100644 index 00000000..b3e1630a --- /dev/null +++ b/agent-library/agents/limit-order-sma/test-limit-order-sma-agent.mjs @@ -0,0 +1,51 @@ +import assert from 'node:assert/strict'; +import { getSystemPrompt, augmentSignals, fetchEthPriceDataFromCoinGecko } from './agent.js'; + +async function run() { + const prompt = getSystemPrompt({ + proposeEnabled: true, + disputeEnabled: true, + commitmentText: 'When ethPriceUSD <= smaEth200USD, swap 1 USDC for WETH.', + }); + + assert.ok(prompt.includes('smaEth200USD')); + assert.ok(prompt.includes('ethPriceUSD')); + assert.ok(prompt.includes('safeWethHuman')); + assert.ok(prompt.includes('safeUsdcHuman')); + assert.ok(prompt.includes('commitment')); + assert.ok(prompt.includes('uniswap_v3_exact_input_single')); + assert.ok(prompt.includes('make_deposit')); + assert.ok(prompt.includes('200-day')); + assert.ok(prompt.includes('ethPriceUSD <= smaEth200USD')); + + const signals = [{ kind: 'deposit' }]; + const augmented = augmentSignals(signals); + assert.equal(augmented.length, 2); + const priceSignal = augmented.find((s) => s.kind === 'priceSignal'); + assert.ok(priceSignal); + assert.ok(typeof priceSignal.currentTimestamp === 'number'); + assert.ok(typeof priceSignal.lastPollTimestamp === 'number'); + + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => ({ + ok: true, + json: async () => ({ + prices: Array.from({ length: 150 }, (_, i) => [ + Date.now() - (150 - i) * 86400 * 1000, + 2000 + i * 0.1, + ]), + }), + }); + try { + const { ethPriceUSD, smaEth200USD, fetchedAt } = await fetchEthPriceDataFromCoinGecko(); + assert.ok(typeof ethPriceUSD === 'number' && ethPriceUSD > 0); + assert.ok(typeof smaEth200USD === 'number' && smaEth200USD > 0); + assert.ok(typeof fetchedAt === 'number'); + } finally { + globalThis.fetch = originalFetch; + } + + console.log('[test] limit-order-sma agent OK'); +} + +run(); diff --git a/agent-library/agents/limit-order/agent.js b/agent-library/agents/limit-order/agent.js new file mode 100644 index 00000000..0537ae7c --- /dev/null +++ b/agent-library/agents/limit-order/agent.js @@ -0,0 +1,490 @@ +// Limit Order Agent - Single limit order on Sepolia (WETH/USDC) + +import { erc20Abi, parseAbi, parseAbiItem } from 'viem'; +import { normalizeAddressOrThrow } from '../../../agent/src/lib/utils.js'; + +const TOKENS = Object.freeze({ + WETH: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + USDC: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238', +}); +const CHAINLINK_ETH_USD_FEED_SEPOLIA = '0x694AA1769357215DE4FAC081bf1f309aDC325306'; +const DEFAULT_ROUTER = '0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e'; +const ALLOWED_ROUTERS = new Set([DEFAULT_ROUTER]); +const ALLOWED_FEE_TIERS = new Set([500, 3000, 10000]); +const QUOTER_CANDIDATES_BY_CHAIN = new Map([ + [1, ['0x61fFE014bA17989E743c5F6cB21bF9697530B21e', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6']], + [11155111, ['0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6']], +]); +const SLIPPAGE_BPS = 50; +const LIMIT_PRICE_USD = 2000; + +const chainlinkAbi = parseAbi([ + 'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', +]); +const quoterV2Abi = [ + { + type: 'function', + name: 'quoteExactInputSingle', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'params', + type: 'tuple', + components: [ + { name: 'tokenIn', type: 'address' }, + { name: 'tokenOut', type: 'address' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'fee', type: 'uint24' }, + { name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + }, + ], + outputs: [ + { name: 'amountOut', type: 'uint256' }, + { name: 'sqrtPriceX96After', type: 'uint160' }, + { name: 'initializedTicksCrossed', type: 'uint32' }, + { name: 'gasEstimate', type: 'uint256' }, + ], + }, +]; +const quoterV1Abi = [ + { + type: 'function', + name: 'quoteExactInputSingle', + stateMutability: 'view', + inputs: [ + { name: 'tokenIn', type: 'address' }, + { name: 'tokenOut', type: 'address' }, + { name: 'fee', type: 'uint24' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + outputs: [{ name: 'amountOut', type: 'uint256' }], + }, +]; + +let lastPollTimestamp = Date.now(); +let limitOrderState = { + proposalBuilt: false, + proposalPosted: false, + orderFilled: false, + proposalSubmitHash: null, + proposalSubmitMs: null, +}; +let hydratedFromChain = false; +const normalizeAddress = (value) => normalizeAddressOrThrow(value, { requireHex: false }); + +const proposalExecutedEvent = parseAbiItem( + 'event ProposalExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId)' +); + +async function getEthPriceUSD(publicClient, chainlinkFeedAddress) { + const result = await publicClient.readContract({ + address: chainlinkFeedAddress, + abi: chainlinkAbi, + functionName: 'latestRoundData', + }); + const answer = result[1]; + if (answer <= 0n) { + throw new Error('Invalid Chainlink ETH/USD price'); + } + return Number(answer) / 1e8; +} + +async function getEthPriceUSDFallback() { + const response = await fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd' + ); + if (!response.ok) { + throw new Error(`Coingecko API error: ${response.status}`); + } + const data = await response.json(); + if (!data?.ethereum?.usd) { + throw new Error('Invalid Coingecko response'); + } + return data.ethereum.usd; +} + +async function resolveQuoterCandidates({ publicClient, config }) { + if (config?.uniswapV3Quoter) { + return [normalizeAddress(String(config.uniswapV3Quoter))]; + } + const chainId = await publicClient.getChainId(); + const byChain = QUOTER_CANDIDATES_BY_CHAIN.get(Number(chainId)); + if (!Array.isArray(byChain) || byChain.length === 0) { + throw new Error(`No Uniswap V3 quoter configured for chainId ${chainId}. Set UNISWAP_V3_QUOTER.`); + } + return byChain.map((v) => normalizeAddress(v)); +} + +async function tryQuoteV2({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }) { + const quoteCall = await publicClient.simulateContract({ + address: quoter, + abi: quoterV2Abi, + functionName: 'quoteExactInputSingle', + args: [{ tokenIn, tokenOut, fee, amountIn, sqrtPriceLimitX96: 0n }], + }); + const result = quoteCall?.result; + return Array.isArray(result) && result.length > 0 ? BigInt(result[0]) : BigInt(result ?? 0n); +} + +async function tryQuoteV1({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }) { + const quoteCall = await publicClient.simulateContract({ + address: quoter, + abi: quoterV1Abi, + functionName: 'quoteExactInputSingle', + args: [tokenIn, tokenOut, fee, amountIn, 0n], + }); + return BigInt(quoteCall?.result ?? 0n); +} + +async function quoteMinOutWithSlippage({ publicClient, config, tokenIn, tokenOut, fee, amountIn }) { + const quoters = await resolveQuoterCandidates({ publicClient, config }); + let quotedAmountOut = 0n; + let selectedQuoter = null; + const failures = []; + + for (const quoter of quoters) { + try { + quotedAmountOut = await tryQuoteV2({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }); + selectedQuoter = quoter; + break; + } catch (v2Error) { + try { + quotedAmountOut = await tryQuoteV1({ publicClient, quoter, tokenIn, tokenOut, fee, amountIn }); + selectedQuoter = quoter; + break; + } catch (v1Error) { + failures.push( + `${quoter}: ${v1Error?.message ?? v2Error?.message ?? 'quote failed'}` + ); + } + } + } + + if (!selectedQuoter) { + throw new Error(`No compatible Uniswap quoter found. Tried: ${failures.join(' | ')}`); + } + if (quotedAmountOut <= 0n) { + throw new Error('Uniswap quoter returned zero output for this swap.'); + } + const minAmountOut = (quotedAmountOut * BigInt(10_000 - SLIPPAGE_BPS)) / 10_000n; + if (minAmountOut <= 0n) { + throw new Error('Swap output too small after slippage; refusing proposal.'); + } + return { minAmountOut }; +} + +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are a limit order agent. Read the commitment to extract the limit price, comparator (e.g. less than or equal to, greater than or equal to), and swap amount.', + 'Compare ethPriceUSD (from signals, Chainlink ETH/USD) to the limit in the commitment. When the condition is satisfied, propose a single swap of the Safe\'s funds.', + 'No deposits. The Safe must be pre-funded. Recipient of the swap is always the Safe (commitmentSafe).', + 'Read signals: ethPriceUSD (Chainlink), safeWethHuman, safeUsdcHuman (human-readable balances), limitOrderState, pendingProposal.', + 'If the price condition is met and orderFilled is false and Safe has sufficient balance and no pendingProposal, call build_og_transactions with one uniswap_v3_exact_input_single action, then post_bond_and_propose.', + 'Extract tokenIn, tokenOut, amountInWei from the commitment. Set recipient to commitmentSafe. Use router at 0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e and an allowlisted fee tier (500, 3000, 10000).', + 'If the price condition is not met, or orderFilled is true, or pendingProposal, or insufficient balance, output action=ignore.', + 'Single execution only. Never propose make_deposit; reject any such tool call.', + mode, + commitmentText ? `\nCommitment:\n${commitmentText}` : '', + 'If no action is needed, output strict JSON with keys: action (propose|dispute|ignore|other) and rationale (string).', + ] + .filter(Boolean) + .join(' '); +} + +function augmentSignals(signals) { + const now = Date.now(); + lastPollTimestamp = now; + return [ + ...signals, + { + kind: 'priceSignal', + currentTimestamp: now, + lastPollTimestamp: now, + }, + ]; +} + +async function enrichSignals(signals, { publicClient, config, account, onchainPendingProposal }) { + if (!signals.some((s) => s.kind === 'priceSignal')) { + return signals; + } + + let ethPriceUSD; + try { + const feed = config?.chainlinkPriceFeed ?? CHAINLINK_ETH_USD_FEED_SEPOLIA; + ethPriceUSD = await getEthPriceUSD(publicClient, feed); + } catch { + ethPriceUSD = await getEthPriceUSDFallback(); + } + + const [safeWethWei, safeUsdcWei] = await Promise.all([ + publicClient.readContract({ + address: TOKENS.WETH, + abi: erc20Abi, + functionName: 'balanceOf', + args: [config.commitmentSafe], + }), + publicClient.readContract({ + address: TOKENS.USDC, + abi: erc20Abi, + functionName: 'balanceOf', + args: [config.commitmentSafe], + }), + ]); + + const safeWethHuman = Number(safeWethWei) / 1e18; + const safeUsdcHuman = Number(safeUsdcWei) / 1e6; + const pendingProposal = Boolean(onchainPendingProposal || limitOrderState.proposalPosted); + + return signals.map((signal) => { + if (signal.kind !== 'priceSignal') return signal; + return { + ...signal, + ethPriceUSD, + safeWethHuman, + safeUsdcHuman, + limitOrderState: { ...limitOrderState }, + pendingProposal, + }; + }); +} + +function parseCallArgs(call) { + if (call?.parsedArguments && typeof call.parsedArguments === 'object') { + return call.parsedArguments; + } + if (typeof call?.arguments === 'string') { + try { + return JSON.parse(call.arguments); + } catch { + return null; + } + } + return null; +} + +async function validateToolCalls({ + toolCalls, + signals, + commitmentText, + commitmentSafe, + publicClient, + config, + onchainPendingProposal, +}) { + const validated = []; + const safeAddress = commitmentSafe ? String(commitmentSafe).toLowerCase() : null; + + for (const call of toolCalls) { + if (call.name === 'dispute_assertion') { + validated.push(call); + continue; + } + if (call.name === 'make_deposit') { + throw new Error('Limit order agent does not use make_deposit; reject.'); + } + if (call.name === 'post_bond_and_propose') { + continue; + } + if (call.name !== 'build_og_transactions') { + continue; + } + + if (onchainPendingProposal) { + throw new Error('Pending proposal exists onchain; execute it before creating a new proposal.'); + } + if (limitOrderState.orderFilled) { + throw new Error('Limit order already filled; single-fire lock.'); + } + if (limitOrderState.proposalPosted) { + throw new Error('Proposal already submitted; wait for execution.'); + } + + const args = parseCallArgs(call); + if (!args || !Array.isArray(args.actions) || args.actions.length !== 1) { + throw new Error('build_og_transactions must include exactly one swap action.'); + } + + const action = args.actions[0]; + if (action.kind !== 'uniswap_v3_exact_input_single') { + throw new Error('Only uniswap_v3_exact_input_single is allowed.'); + } + + const tokenIn = normalizeAddress(String(action.tokenIn)); + const tokenOut = normalizeAddress(String(action.tokenOut)); + const recipient = normalizeAddress(String(action.recipient ?? safeAddress)); + const router = normalizeAddress(String(action.router ?? DEFAULT_ROUTER)); + const fee = Number(action.fee ?? 3000); + const amountInWei = BigInt(String(action.amountInWei)); + + if (!action.tokenIn || !action.tokenOut || !action.amountInWei) { + throw new Error('action must include tokenIn, tokenOut, and amountInWei.'); + } + if (tokenIn !== TOKENS.WETH && tokenIn !== TOKENS.USDC) { + throw new Error('tokenIn must be Sepolia WETH or USDC.'); + } + if (tokenOut !== TOKENS.WETH && tokenOut !== TOKENS.USDC) { + throw new Error('tokenOut must be Sepolia WETH or USDC.'); + } + if (tokenIn === tokenOut) { + throw new Error('tokenIn and tokenOut must differ.'); + } + if (recipient !== safeAddress) { + throw new Error('Recipient must be the commitment Safe.'); + } + if (!ALLOWED_ROUTERS.has(router)) { + throw new Error(`Router ${router} is not allowlisted.`); + } + if (!ALLOWED_FEE_TIERS.has(fee)) { + throw new Error(`Fee tier ${fee} is not allowlisted.`); + } + if (amountInWei <= 0n) { + throw new Error('amountInWei must be positive.'); + } + + const inputBalance = await publicClient.readContract({ + address: tokenIn, + abi: erc20Abi, + functionName: 'balanceOf', + args: [commitmentSafe], + }); + if (BigInt(inputBalance) < amountInWei) { + throw new Error('Safe has insufficient balance for this swap.'); + } + + const { minAmountOut } = await quoteMinOutWithSlippage({ + publicClient, + config, + tokenIn, + tokenOut, + fee, + amountIn: amountInWei, + }); + + action.tokenIn = tokenIn; + action.tokenOut = tokenOut; + action.router = router; + action.recipient = safeAddress; + action.operation = 0; + action.fee = fee; + action.amountInWei = amountInWei.toString(); + action.amountOutMinWei = minAmountOut.toString(); + args.actions[0] = action; + + let ethPriceUSD; + try { + const feed = config?.chainlinkPriceFeed ?? CHAINLINK_ETH_USD_FEED_SEPOLIA; + ethPriceUSD = await getEthPriceUSD(publicClient, feed); + } catch { + ethPriceUSD = await getEthPriceUSDFallback(); + } + if (ethPriceUSD > LIMIT_PRICE_USD) { + throw new Error( + `Price condition not met: ethPriceUSD ${ethPriceUSD} exceeds limit ${LIMIT_PRICE_USD}` + ); + } + + validated.push({ ...call, parsedArguments: args }); + } + + return validated; +} + +function onToolOutput({ name, parsedOutput }) { + if (!name || !parsedOutput || parsedOutput.status === 'error') return; + + if (name === 'build_og_transactions' && parsedOutput.status === 'ok') { + limitOrderState.proposalBuilt = true; + return; + } + + if (name === 'post_bond_and_propose' && parsedOutput.status === 'submitted') { + const submitHash = parsedOutput.transactionHash ?? parsedOutput.proposalHash ?? null; + if (submitHash) { + limitOrderState.proposalPosted = true; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = submitHash; + limitOrderState.proposalSubmitMs = Date.now(); + } + } +} + +function onProposalEvents({ executedProposalCount = 0, deletedProposalCount = 0 }) { + if (executedProposalCount > 0) { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + limitOrderState.orderFilled = true; + } + if (deletedProposalCount > 0) { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } +} + +async function reconcileProposalSubmission({ publicClient, ogModule, startBlock }) { + if (!hydratedFromChain && ogModule) { + try { + const toBlock = await publicClient.getBlockNumber(); + const fromBlock = startBlock ?? 0n; + const logs = await publicClient.getLogs({ + address: ogModule, + event: proposalExecutedEvent, + fromBlock, + toBlock, + }); + if (logs.length > 0) { + limitOrderState.orderFilled = true; + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } + hydratedFromChain = true; + } catch (err) { + console.warn('[limit-order] Failed to hydrate from chain:', err?.message ?? err); + } + } + if (!limitOrderState.proposalPosted || !limitOrderState.proposalSubmitHash) return; + try { + const receipt = await publicClient.getTransactionReceipt({ + hash: limitOrderState.proposalSubmitHash, + }); + if (receipt?.status === 0n || receipt?.status === 'reverted') { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } + } catch { + if (Date.now() - (limitOrderState.proposalSubmitMs ?? 0) > 60_000) { + limitOrderState.proposalPosted = false; + limitOrderState.proposalBuilt = false; + limitOrderState.proposalSubmitHash = null; + limitOrderState.proposalSubmitMs = null; + } + } +} + +export { + getSystemPrompt, + augmentSignals, + enrichSignals, + validateToolCalls, + onToolOutput, + onProposalEvents, + reconcileProposalSubmission, +}; diff --git a/agent-library/agents/limit-order/agent.json b/agent-library/agents/limit-order/agent.json new file mode 100644 index 00000000..71d440aa --- /dev/null +++ b/agent-library/agents/limit-order/agent.json @@ -0,0 +1 @@ +{"type":"https://eips.ethereum.org/EIPS/eip-8004#registration-v1","name":"Oya Limit Order Agent","description":"Single limit order: when Chainlink price target is hit, proposes uniswap_v3_exact_input_single so the Safe swaps its pre-funded WETH or USDC. Recipient = Safe.","image":"https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/default/agent.png","endpoints":[{"name":"agentWallet","endpoint":"eip155:11155111:0x0000000000000000000000000000000000000000"}],"registrations":[]} diff --git a/agent-library/agents/limit-order/commitment.txt b/agent-library/agents/limit-order/commitment.txt new file mode 100644 index 00000000..c828afa4 --- /dev/null +++ b/agent-library/agents/limit-order/commitment.txt @@ -0,0 +1,6 @@ +Limit Order Rules: Purchase 1 USDC of WETH if WETH price is below $2000. + +When the ETH/USD price is less than or equal to 2000, the agent should propose a swap of 1 USDC for WETH using this Safe's funds. +The Safe must be pre-funded with sufficient USDC. Swap output (WETH) goes to this Safe. +Token addresses: WETH 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9, USDC 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238. +Only execute one swap. Max slippage 0.50%. diff --git a/agent-library/agents/limit-order/simulate-limit-order.mjs b/agent-library/agents/limit-order/simulate-limit-order.mjs new file mode 100644 index 00000000..e227390e --- /dev/null +++ b/agent-library/agents/limit-order/simulate-limit-order.mjs @@ -0,0 +1,46 @@ +/** + * Simulates when a limit order would trigger based on ethPrice vs limit. + * lte: trigger when ethPrice <= limitPrice (buy when cheap) + * gte: trigger when ethPrice >= limitPrice (sell when expensive) + */ +function wouldTrigger({ ethPrice, limitPrice, comparator }) { + if (comparator === 'lte') return ethPrice <= limitPrice; + if (comparator === 'gte') return ethPrice >= limitPrice; + throw new Error(`Unknown comparator: ${comparator}`); +} + +function run() { + const scenarios = [ + { + name: 'lte: price below limit -> trigger', + input: { ethPrice: 1999, limitPrice: 2000, comparator: 'lte' }, + }, + { + name: 'lte: price at limit -> trigger', + input: { ethPrice: 2000, limitPrice: 2000, comparator: 'lte' }, + }, + { + name: 'lte: price above limit -> no trigger', + input: { ethPrice: 2001, limitPrice: 2000, comparator: 'lte' }, + }, + { + name: 'gte: price above limit -> trigger', + input: { ethPrice: 2500, limitPrice: 2000, comparator: 'gte' }, + }, + { + name: 'gte: price at limit -> trigger', + input: { ethPrice: 2000, limitPrice: 2000, comparator: 'gte' }, + }, + { + name: 'gte: price below limit -> no trigger', + input: { ethPrice: 1500, limitPrice: 2000, comparator: 'gte' }, + }, + ]; + + for (const scenario of scenarios) { + const result = wouldTrigger(scenario.input); + console.log(`[sim] ${scenario.name}:`, result); + } +} + +run(); diff --git a/agent-library/agents/limit-order/test-limit-order-agent.mjs b/agent-library/agents/limit-order/test-limit-order-agent.mjs new file mode 100644 index 00000000..465fdb5d --- /dev/null +++ b/agent-library/agents/limit-order/test-limit-order-agent.mjs @@ -0,0 +1,30 @@ +import assert from 'node:assert/strict'; +import { getSystemPrompt, augmentSignals } from './agent.js'; + +function run() { + const prompt = getSystemPrompt({ + proposeEnabled: true, + disputeEnabled: true, + commitmentText: 'When ETH/USD is less than or equal to 2000, swap 1 USDC for WETH.', + }); + + assert.ok(prompt.includes('limit order agent')); + assert.ok(prompt.includes('ethPriceUSD')); + assert.ok(prompt.includes('safeWethHuman')); + assert.ok(prompt.includes('safeUsdcHuman')); + assert.ok(prompt.includes('commitment')); + assert.ok(prompt.includes('uniswap_v3_exact_input_single')); + assert.ok(prompt.includes('make_deposit')); + + const signals = [{ kind: 'deposit' }]; + const augmented = augmentSignals(signals); + assert.equal(augmented.length, 2); + const priceSignal = augmented.find((s) => s.kind === 'priceSignal'); + assert.ok(priceSignal); + assert.ok(typeof priceSignal.currentTimestamp === 'number'); + assert.ok(typeof priceSignal.lastPollTimestamp === 'number'); + + console.log('[test] limit-order agent OK'); +} + +run(); diff --git a/agent-library/agents/price-race-swap/agent.js b/agent-library/agents/price-race-swap/agent.js new file mode 100644 index 00000000..d21cbc46 --- /dev/null +++ b/agent-library/agents/price-race-swap/agent.js @@ -0,0 +1,752 @@ +import { readFile, unlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + normalizeAddressOrThrow, + normalizeHashOrNull, +} from '../../../agent/src/lib/utils.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const TOKENS = Object.freeze({ + WETH: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + USDC: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238', + UNI: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', +}); + +const ALLOWED_ROUTERS = new Set([ + '0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e', +]); +const DEFAULT_ROUTER = '0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e'; +const ALLOWED_FEE_TIERS = new Set([500, 3000, 10000]); +const QUOTER_CANDIDATES_BY_CHAIN = new Map([ + [1, ['0x61fFE014bA17989E743c5F6cB21bF9697530B21e', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6']], + [11155111, ['0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3', '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6']], +]); +const quoterV2Abi = [ + { + type: 'function', + name: 'quoteExactInputSingle', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'params', + type: 'tuple', + components: [ + { name: 'tokenIn', type: 'address' }, + { name: 'tokenOut', type: 'address' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'fee', type: 'uint24' }, + { name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + }, + ], + outputs: [ + { name: 'amountOut', type: 'uint256' }, + { name: 'sqrtPriceX96After', type: 'uint160' }, + { name: 'initializedTicksCrossed', type: 'uint32' }, + { name: 'gasEstimate', type: 'uint256' }, + ], + }, +]; +const quoterV1Abi = [ + { + type: 'function', + name: 'quoteExactInputSingle', + stateMutability: 'view', + inputs: [ + { name: 'tokenIn', type: 'address' }, + { name: 'tokenOut', type: 'address' }, + { name: 'fee', type: 'uint24' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + outputs: [{ name: 'amountOut', type: 'uint256' }], + }, +]; +const erc20BalanceOfAbi = [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ name: '', type: 'uint256' }], + }, +]; +const proposalExecutedEvent = { + type: 'event', + name: 'ProposalExecuted', + inputs: [ + { indexed: true, name: 'proposalHash', type: 'bytes32' }, + { indexed: true, name: 'assertionId', type: 'bytes32' }, + ], + anonymous: false, +}; + +const inferredTriggersCache = new Map(); +const singleFireState = { + proposalSubmitted: false, + proposalHash: null, +}; +let singleFireStateHydrated = false; +let singleFireReconciledOnchain = false; +const normalizeAddress = normalizeAddressOrThrow; +const normalizeHash = normalizeHashOrNull; + +function getSingleFireStatePath() { + const fromEnv = process.env.PRICE_RACE_SWAP_STATE_FILE; + if (fromEnv && String(fromEnv).trim().length > 0) { + return path.resolve(String(fromEnv).trim()); + } + return path.join(__dirname, '.single-fire-state.json'); +} + +function resolveSubmittedProposalHash(parsedOutput) { + const txHash = normalizeHash(parsedOutput?.transactionHash); + const explicitOgHash = normalizeHash(parsedOutput?.ogProposalHash); + if (explicitOgHash) return explicitOgHash; + + const legacyHash = normalizeHash(parsedOutput?.proposalHash); + if (legacyHash && (!txHash || legacyHash !== txHash)) { + return legacyHash; + } + + // Backward-compatible fallback when only tx hash is available. + return txHash ?? legacyHash; +} + +async function persistSingleFireState() { + const payload = JSON.stringify( + { + proposalSubmitted: singleFireState.proposalSubmitted, + proposalHash: singleFireState.proposalHash, + }, + null, + 2 + ); + await writeFile(getSingleFireStatePath(), payload, 'utf8'); +} + +async function hydrateSingleFireState() { + if (singleFireStateHydrated) return; + singleFireStateHydrated = true; + try { + const raw = await readFile(getSingleFireStatePath(), 'utf8'); + const parsed = JSON.parse(raw); + singleFireState.proposalSubmitted = Boolean(parsed?.proposalSubmitted); + singleFireState.proposalHash = normalizeHash(parsed?.proposalHash) ?? null; + } catch (error) { + // Missing/corrupt state file means unlocked unless chain reconciliation proves otherwise. + } +} + +async function lockSingleFire({ proposalHash = null } = {}) { + singleFireState.proposalSubmitted = true; + singleFireState.proposalHash = normalizeHash(proposalHash) ?? singleFireState.proposalHash; + await persistSingleFireState(); +} + +async function reconcileSingleFireFromChain({ publicClient }) { + if (singleFireReconciledOnchain) return; + singleFireReconciledOnchain = true; + if (!publicClient || singleFireState.proposalSubmitted) return; + + const rawOgModule = process.env.OG_MODULE; + if (!rawOgModule) return; + + let ogModule; + try { + ogModule = normalizeAddress(String(rawOgModule)); + } catch (error) { + return; + } + + const latestBlock = await publicClient.getBlockNumber(); + const configuredStart = process.env.START_BLOCK ? BigInt(process.env.START_BLOCK) : 0n; + const chunkSize = 50_000n; + let currentTo = latestBlock; + + while (currentTo >= configuredStart) { + const minFrom = currentTo >= chunkSize - 1n ? currentTo - chunkSize + 1n : 0n; + const currentFrom = minFrom < configuredStart ? configuredStart : minFrom; + const logs = await publicClient.getLogs({ + address: ogModule, + event: proposalExecutedEvent, + fromBlock: currentFrom, + toBlock: currentTo, + }); + if (logs.length > 0) { + const hash = normalizeHash(logs[logs.length - 1]?.args?.proposalHash); + await lockSingleFire({ proposalHash: hash }); + return; + } + if (currentFrom === configuredStart) break; + currentTo = currentFrom - 1n; + } +} + +function normalizeComparator(value) { + const normalized = String(value ?? '').trim().toLowerCase(); + if (normalized === 'gte' || normalized === '>=') return 'gte'; + if (normalized === 'lte' || normalized === '<=') return 'lte'; + throw new Error(`Unsupported comparator: ${value}`); +} + +function extractFirstText(responseJson) { + const outputs = responseJson?.output; + if (!Array.isArray(outputs)) return ''; + + for (const item of outputs) { + if (!item?.content) continue; + for (const chunk of item.content) { + if (chunk?.text) return chunk.text; + if (chunk?.output_text) return chunk.output_text?.text ?? ''; + if (chunk?.text?.value) return chunk.text.value; + } + } + + return ''; +} + +function sanitizeInferredTriggers(rawTriggers) { + if (!Array.isArray(rawTriggers)) { + return []; + } + + const normalized = rawTriggers.map((trigger, index) => { + if (!trigger || typeof trigger !== 'object') { + throw new Error(`Inferred trigger at index ${index} is not an object.`); + } + + const baseToken = normalizeAddress(String(trigger.baseToken)); + const quoteToken = normalizeAddress(String(trigger.quoteToken)); + if (baseToken === quoteToken) { + throw new Error(`Inferred trigger ${index} uses the same base and quote token.`); + } + + const threshold = Number(trigger.threshold); + if (!Number.isFinite(threshold) || threshold <= 0) { + throw new Error(`Inferred trigger ${index} has invalid threshold.`); + } + + const priorityRaw = trigger.priority ?? index; + const priority = Number(priorityRaw); + if (!Number.isInteger(priority) || priority < 0) { + throw new Error(`Inferred trigger ${index} has invalid priority.`); + } + + const out = { + id: trigger.id ? String(trigger.id) : `inferred-trigger-${index + 1}`, + label: trigger.label ? String(trigger.label) : undefined, + baseToken, + quoteToken, + comparator: normalizeComparator(trigger.comparator), + threshold, + priority, + // Keep price conditions level-triggered so a later deposit can still act. + emitOnce: trigger.emitOnce === undefined ? false : Boolean(trigger.emitOnce), + }; + + if (trigger.pool) { + out.pool = normalizeAddress(String(trigger.pool)); + } else { + out.poolSelection = 'high-liquidity'; + } + + return out; + }); + + const seenIds = new Set(); + for (const trigger of normalized) { + if (seenIds.has(trigger.id)) { + throw new Error(`Duplicate inferred trigger id: ${trigger.id}`); + } + seenIds.add(trigger.id); + } + + normalized.sort((a, b) => { + const priorityCmp = a.priority - b.priority; + if (priorityCmp !== 0) return priorityCmp; + return a.id.localeCompare(b.id); + }); + + return normalized; +} + +async function getPriceTriggers({ commitmentText, config }) { + if (!commitmentText || !config?.openAiApiKey) { + return []; + } + + if (inferredTriggersCache.has(commitmentText)) { + return inferredTriggersCache.get(commitmentText); + } + + const payload = { + model: config.openAiModel, + input: [ + { + role: 'system', + content: + 'Extract exactly two Uniswap V3 price race triggers from this plain-language commitment. Return strict JSON: {"triggers":[...]}. Each trigger must include: id, label, baseToken, quoteToken, comparator (gte|lte), threshold (number), priority (number), and optional pool (address). If pool is not explicit in the commitment, omit it and high-liquidity pool selection will be used. Use only addresses and conditions present in the commitment text. Do not invent pools, tokens, or thresholds.', + }, + { + role: 'user', + content: commitmentText, + }, + ], + text: { format: { type: 'json_object' } }, + }; + + const res = await fetch(`${config.openAiBaseUrl}/responses`, { + method: 'POST', + headers: { + Authorization: `Bearer ${config.openAiApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`OpenAI API error while inferring triggers: ${res.status} ${text}`); + } + + const json = await res.json(); + const raw = extractFirstText(json); + if (!raw) { + inferredTriggersCache.set(commitmentText, []); + return []; + } + + let parsed; + try { + parsed = JSON.parse(raw); + } catch (error) { + throw new Error(`Failed to parse inferred trigger JSON: ${raw}`); + } + + const triggers = sanitizeInferredTriggers(parsed?.triggers ?? []); + inferredTriggersCache.set(commitmentText, triggers); + return triggers; +} + +function parseCallArgs(call) { + if (call?.parsedArguments && typeof call.parsedArguments === 'object') { + return call.parsedArguments; + } + if (typeof call?.arguments === 'string') { + try { + return JSON.parse(call.arguments); + } catch (error) { + return null; + } + } + return null; +} + +function isMatchingPriceSignal(signal, actionFee, tokenIn, tokenOut) { + if (!signal || signal.kind !== 'priceTrigger') return false; + + const sBase = String(signal.baseToken ?? '').toLowerCase(); + const sQuote = String(signal.quoteToken ?? '').toLowerCase(); + const pairMatches = + (sBase === tokenIn && sQuote === tokenOut) || + (sBase === tokenOut && sQuote === tokenIn); + if (!pairMatches) return false; + + if (signal.poolFee === undefined || signal.poolFee === null) return false; + return Number(signal.poolFee) === actionFee; +} + +function pickWinningPriceTrigger(signals) { + const triggers = Array.isArray(signals) + ? signals.filter((signal) => signal?.kind === 'priceTrigger') + : []; + if (triggers.length === 0) return null; + const sorted = [...triggers].sort((a, b) => { + const pa = Number(a?.priority ?? Number.MAX_SAFE_INTEGER); + const pb = Number(b?.priority ?? Number.MAX_SAFE_INTEGER); + if (pa !== pb) return pa - pb; + return String(a?.triggerId ?? '').localeCompare(String(b?.triggerId ?? '')); + }); + return sorted[0]; +} + +function pickWethSnapshot(signals) { + if (!Array.isArray(signals)) return null; + for (const signal of signals) { + if (signal?.kind !== 'erc20BalanceSnapshot') continue; + if (String(signal.asset ?? '').toLowerCase() !== TOKENS.WETH) continue; + return signal; + } + return null; +} + +async function resolveWethAmount({ + wethSnapshot, + publicClient, + commitmentSafe, +}) { + if (wethSnapshot?.amount !== undefined && wethSnapshot?.amount !== null) { + return BigInt(String(wethSnapshot.amount)); + } + if (!publicClient) { + throw new Error('No WETH balance snapshot and no public client available for fallback read.'); + } + if (!commitmentSafe) { + throw new Error('No WETH balance snapshot and no commitment Safe address for fallback read.'); + } + const current = await publicClient.readContract({ + address: TOKENS.WETH, + abi: erc20BalanceOfAbi, + functionName: 'balanceOf', + args: [commitmentSafe], + }); + return BigInt(current); +} + +async function resolveQuoterCandidates({ publicClient, config }) { + if (!publicClient) { + throw new Error('publicClient is required for Uniswap quoter reads.'); + } + if (config?.uniswapV3Quoter) { + return [normalizeAddress(String(config.uniswapV3Quoter))]; + } + const chainId = await publicClient.getChainId(); + const byChain = QUOTER_CANDIDATES_BY_CHAIN.get(Number(chainId)); + if (!Array.isArray(byChain) || byChain.length === 0) { + throw new Error( + `No Uniswap V3 quoter configured for chainId ${chainId}. Set UNISWAP_V3_QUOTER.` + ); + } + return byChain.map((value) => normalizeAddress(value)); +} + +async function tryQuoteExactInputSingleV2({ + publicClient, + quoter, + tokenIn, + tokenOut, + fee, + amountIn, +}) { + const quoteCall = await publicClient.simulateContract({ + address: quoter, + abi: quoterV2Abi, + functionName: 'quoteExactInputSingle', + args: [ + { + tokenIn, + tokenOut, + fee, + amountIn, + sqrtPriceLimitX96: 0n, + }, + ], + }); + const result = quoteCall?.result; + return Array.isArray(result) && result.length > 0 ? BigInt(result[0]) : BigInt(result ?? 0n); +} + +async function tryQuoteExactInputSingleV1({ + publicClient, + quoter, + tokenIn, + tokenOut, + fee, + amountIn, +}) { + const quoteCall = await publicClient.simulateContract({ + address: quoter, + abi: quoterV1Abi, + functionName: 'quoteExactInputSingle', + args: [tokenIn, tokenOut, fee, amountIn, 0n], + }); + return BigInt(quoteCall?.result ?? 0n); +} + +async function quoteMinOutWithSlippage({ + publicClient, + config, + tokenIn, + tokenOut, + fee, + amountIn, + slippageBps = 50, +}) { + const quoters = await resolveQuoterCandidates({ publicClient, config }); + let quotedAmountOut = 0n; + let selectedQuoter = null; + const failures = []; + + for (const quoter of quoters) { + try { + quotedAmountOut = await tryQuoteExactInputSingleV2({ + publicClient, + quoter, + tokenIn, + tokenOut, + fee, + amountIn, + }); + selectedQuoter = quoter; + break; + } catch (v2Error) { + try { + quotedAmountOut = await tryQuoteExactInputSingleV1({ + publicClient, + quoter, + tokenIn, + tokenOut, + fee, + amountIn, + }); + selectedQuoter = quoter; + break; + } catch (v1Error) { + failures.push( + `${quoter}: ${ + v1Error?.shortMessage ?? + v1Error?.message ?? + v2Error?.shortMessage ?? + v2Error?.message ?? + 'quote failed' + }` + ); + } + } + } + + if (!selectedQuoter) { + throw new Error(`No compatible Uniswap quoter found. Tried: ${failures.join(' | ')}`); + } + + if (quotedAmountOut <= 0n) { + throw new Error('Uniswap quoter returned zero output for this swap.'); + } + const minAmountOut = (quotedAmountOut * BigInt(10_000 - slippageBps)) / 10_000n; + if (minAmountOut <= 0n) { + throw new Error('Swap output is too small after slippage guard; refusing proposal.'); + } + return { quoter: selectedQuoter, quotedAmountOut, minAmountOut }; +} + +async function validateToolCalls({ + toolCalls, + signals, + commitmentText, + commitmentSafe, + publicClient, + config, + onchainPendingProposal, +}) { + await hydrateSingleFireState(); + await reconcileSingleFireFromChain({ publicClient }); + + const validated = []; + const safeAddress = commitmentSafe ? String(commitmentSafe).toLowerCase() : null; + const winningTrigger = pickWinningPriceTrigger(signals); + const wethSnapshot = pickWethSnapshot(signals); + + for (const call of toolCalls) { + if (call.name === 'dispute_assertion') { + validated.push(call); + continue; + } + + if (call.name === 'post_bond_and_propose') { + continue; + } + + if (call.name !== 'build_og_transactions') { + continue; + } + if (onchainPendingProposal) { + throw new Error('Pending proposal exists onchain; execute it before creating a new proposal.'); + } + if (singleFireState.proposalSubmitted) { + throw new Error('Single-fire lock engaged: a proposal was already submitted.'); + } + + const args = parseCallArgs(call); + if (!args || !Array.isArray(args.actions) || args.actions.length !== 1) { + throw new Error('build_og_transactions must include exactly one swap action.'); + } + + const action = args.actions[0]; + if (action.kind !== 'uniswap_v3_exact_input_single') { + throw new Error('Only uniswap_v3_exact_input_single is allowed for this agent.'); + } + + if (!winningTrigger) { + throw new Error('No priceTrigger signal available for this cycle.'); + } + const inferredTokenOut = + String(winningTrigger.baseToken ?? '').toLowerCase() === TOKENS.WETH + ? String(winningTrigger.quoteToken ?? '').toLowerCase() + : String(winningTrigger.baseToken ?? '').toLowerCase(); + + const tokenIn = normalizeAddress(String(action.tokenIn ?? TOKENS.WETH)); + const tokenOut = normalizeAddress(String(action.tokenOut ?? inferredTokenOut)); + const router = DEFAULT_ROUTER; + const recipient = normalizeAddress(String(action.recipient ?? safeAddress)); + const operation = action.operation === undefined ? 0 : Number(action.operation); + const fee = Number(action.fee ?? winningTrigger.poolFee); + const amountIn = await resolveWethAmount({ + wethSnapshot, + publicClient, + commitmentSafe: safeAddress, + }); + const quoted = await quoteMinOutWithSlippage({ + publicClient, + config, + tokenIn, + tokenOut, + fee, + amountIn, + slippageBps: 50, + }); + const amountOutMin = quoted.minAmountOut; + + action.tokenIn = tokenIn; + action.tokenOut = tokenOut; + action.router = router; + action.recipient = recipient; + action.operation = 0; + action.fee = fee; + action.amountInWei = amountIn.toString(); + action.amountOutMinWei = amountOutMin.toString(); + args.actions[0] = action; + + if (!Number.isInteger(operation) || operation !== 0) { + throw new Error('Swap action operation must be 0 (CALL).'); + } + + if (tokenIn !== TOKENS.WETH) { + throw new Error('Swap tokenIn must be Sepolia WETH.'); + } + if (tokenOut !== TOKENS.USDC && tokenOut !== TOKENS.UNI) { + throw new Error('Swap tokenOut must be Sepolia USDC or UNI.'); + } + if (!ALLOWED_ROUTERS.has(router)) { + throw new Error(`Router ${router} is not allowlisted.`); + } + if (safeAddress && recipient !== safeAddress) { + throw new Error('Swap recipient must be the commitment Safe.'); + } + if (!Number.isInteger(fee) || fee <= 0) { + throw new Error('Swap fee must be a positive integer.'); + } + if (!ALLOWED_FEE_TIERS.has(fee)) { + throw new Error('Swap fee tier is not allowlisted.'); + } + if (amountIn <= 0n) { + throw new Error('Swap amountInWei must be > 0.'); + } + if (amountOutMin < 0n) { + throw new Error('Swap amountOutMinWei must be >= 0.'); + } + + const matchesWinningTrigger = isMatchingPriceSignal( + winningTrigger, + fee, + tokenIn, + tokenOut + ); + if (!matchesWinningTrigger) { + throw new Error( + 'Swap action must match the winning priceTrigger signal for this cycle.' + ); + } + + validated.push({ + ...call, + parsedArguments: args, + }); + } + + return validated; +} + +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are a price-race swap agent for a commitment Safe controlled by an Optimistic Governor.', + 'Your own address is provided as agentAddress.', + 'Interpret the commitment as a multi-choice race and execute at most one winning branch.', + 'Single-fire mode is enabled: after one successful proposal submission, do not propose again.', + 'Use your reasoning over the plain-language commitment and incoming signals. Do not depend on rigid text pattern matching.', + 'Treat erc20BalanceSnapshot signals as authoritative current Safe balances for this cycle.', + 'If exactly one priceTrigger signal is present in this cycle, treat it as the winning branch for this cycle.', + 'When a winning priceTrigger is present, use the latest known Safe WETH balance (snapshot if present, otherwise current onchain balance).', + 'First trigger wins. If multiple triggers appear true in one cycle, use signal priority and then lexical triggerId order.', + 'Use all currently available WETH in the Safe for the winning branch swap.', + 'Build one uniswap_v3_exact_input_single action where amountInWei equals the WETH snapshot amount.', + `Set router to Sepolia Uniswap V3 SwapRouter02 at ${DEFAULT_ROUTER}.`, + 'Do not estimate amountOutMinWei from observedPrice.', + 'The runner validates and overwrites amountOutMinWei using Uniswap V3 Quoter with 0.50% slippage protection.', + 'Preferred flow: build_og_transactions with one uniswap_v3_exact_input_single action, then rely on runner propose submission.', + 'Only use allowlisted Sepolia addresses from the commitment context. Never execute both branches.', + 'Use the poolFee from a priceTrigger signal when preparing uniswap_v3_exact_input_single actions.', + 'Never route purchased assets to addresses other than the commitment Safe unless explicitly required by the commitment.', + 'If there is insufficient evidence that a trigger fired first, or route/liquidity/slippage constraints are not safely satisfiable, return ignore.', + 'Default to disputing proposals that violate these rules; prefer no-op when unsure.', + mode, + commitmentText ? `Commitment text:\n${commitmentText}` : '', + 'If no action is needed, output strict JSON with keys: action (propose|deposit|dispute|ignore|other) and rationale (string).', + ] + .filter(Boolean) + .join(' '); +} + +async function onToolOutput({ name, parsedOutput }) { + if (!name || !parsedOutput || parsedOutput.status !== 'submitted') return; + if (name !== 'post_bond_and_propose' && name !== 'auto_post_bond_and_propose') return; + const proposalHash = resolveSubmittedProposalHash(parsedOutput); + if (!proposalHash) return; + await lockSingleFire({ proposalHash }); +} + +function onProposalEvents({ executedProposalCount = 0 }) { + if (executedProposalCount > 0) { + void lockSingleFire(); + } +} + +function getSingleFireState() { + return { ...singleFireState }; +} + +function resetSingleFireState() { + singleFireState.proposalSubmitted = false; + singleFireState.proposalHash = null; + singleFireStateHydrated = true; + singleFireReconciledOnchain = false; + void unlink(getSingleFireStatePath()).catch(() => {}); +} + +async function reconcileProposalSubmission({ publicClient }) { + await hydrateSingleFireState(); + await reconcileSingleFireFromChain({ publicClient }); +} + +export { + getPriceTriggers, + getSystemPrompt, + getSingleFireState, + onToolOutput, + onProposalEvents, + reconcileProposalSubmission, + resetSingleFireState, + sanitizeInferredTriggers, + validateToolCalls, +}; diff --git a/agent-library/agents/price-race-swap/agent.json b/agent-library/agents/price-race-swap/agent.json new file mode 100644 index 00000000..faf91d52 --- /dev/null +++ b/agent-library/agents/price-race-swap/agent.json @@ -0,0 +1,18 @@ +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "Oya Price Race Swap Agent", + "description": "Agent that monitors ETH/USDC and UMA/USDC threshold triggers and executes one first-trigger-wins USDC swap branch on Uniswap.", + "image": "https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/price-race-swap/agent.png", + "endpoints": [ + { + "name": "agentWallet", + "endpoint": "eip155:11155111:0x2967c076182f0303037072670e744e26ed4a830f" + } + ], + "registrations": [ + { + "agentId": 0, + "agentRegistry": "eip155:11155111:0x8004a818bfb912233c491871b3d84c89a494bd9e" + } + ] +} diff --git a/agent-library/agents/price-race-swap/commitment.txt b/agent-library/agents/price-race-swap/commitment.txt new file mode 100644 index 00000000..9dfce469 --- /dev/null +++ b/agent-library/agents/price-race-swap/commitment.txt @@ -0,0 +1,9 @@ +This commitment should use whatever WETH is available in the Safe at the moment of execution. + +Token addresses for this commitment are as follows: WETH is 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9, USDC is 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238, and UNI is 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984. + +If WETH/USDC is greater than or equal to 1800 and this is the first trigger that becomes true, swap all available WETH for USDC using a high-liquidity Uniswap V3 pool. + +If UNI/WETH is less than or equal to 0.03 and this is the first trigger that becomes true, swap all available WETH for UNI using a high-liquidity Uniswap V3 pool. + +Only one branch may execute in total. First trigger wins. Enforce max slippage of 0.50%. If no valid route meets slippage and liquidity constraints, do not trade. diff --git a/agent-library/agents/price-race-swap/simulate-price-race-swap.mjs b/agent-library/agents/price-race-swap/simulate-price-race-swap.mjs new file mode 100644 index 00000000..14aafb19 --- /dev/null +++ b/agent-library/agents/price-race-swap/simulate-price-race-swap.mjs @@ -0,0 +1,58 @@ +function pickWinningBranch({ ethPrice, uniWethPrice, ethThreshold, uniWethThreshold }) { + const ethTriggered = ethPrice >= ethThreshold; + const uniTriggered = uniWethPrice <= uniWethThreshold; + + if (ethTriggered && uniTriggered) { + return { + winner: 'weth-to-usdc', + reason: 'tie-break: WETH/USDC branch wins when both are true in same evaluation cycle', + }; + } + + if (ethTriggered) { + return { + winner: 'weth-to-usdc', + reason: `WETH/USDC ${ethPrice} >= ${ethThreshold}`, + }; + } + + if (uniTriggered) { + return { + winner: 'weth-to-uni', + reason: `UNI/WETH ${uniWethPrice} <= ${uniWethThreshold}`, + }; + } + + return { + winner: 'none', + reason: 'no trigger hit', + }; +} + +function run() { + const scenarios = [ + { + name: 'WETH->USDC wins', + input: { ethPrice: 1850, uniWethPrice: 0.05, ethThreshold: 1800, uniWethThreshold: 0.03 }, + }, + { + name: 'WETH->UNI wins', + input: { ethPrice: 1700, uniWethPrice: 0.02, ethThreshold: 1800, uniWethThreshold: 0.03 }, + }, + { + name: 'Tie -> WETH->USDC wins', + input: { ethPrice: 1800, uniWethPrice: 0.03, ethThreshold: 1800, uniWethThreshold: 0.03 }, + }, + { + name: 'No trigger', + input: { ethPrice: 1700, uniWethPrice: 0.04, ethThreshold: 1800, uniWethThreshold: 0.03 }, + }, + ]; + + for (const scenario of scenarios) { + const result = pickWinningBranch(scenario.input); + console.log(`[sim] ${scenario.name}:`, result); + } +} + +run(); diff --git a/agent-library/agents/price-race-swap/test-allowlist.mjs b/agent-library/agents/price-race-swap/test-allowlist.mjs new file mode 100644 index 00000000..c7ffffa6 --- /dev/null +++ b/agent-library/agents/price-race-swap/test-allowlist.mjs @@ -0,0 +1,260 @@ +import assert from 'node:assert/strict'; +import { + getSingleFireState, + onProposalEvents, + onToolOutput, + reconcileProposalSubmission, + resetSingleFireState, + validateToolCalls, +} from './agent.js'; + +const WETH = '0x7b79995e5f793a07bc00c21412e50ecae098e7f9'; +const USDC = '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238'; +const UNI = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; +const ROUTER = '0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e'; +const QUOTER = '0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3'; +const POOL = '0x6418eec70f50913ff0d756b48d32ce7c02b47c47'; + +async function run() { + process.env.PRICE_RACE_SWAP_STATE_FILE = '/tmp/price-race-swap-state-test.json'; + resetSingleFireState(); + + const toolCalls = [ + { + name: 'build_og_transactions', + callId: '1', + parsedArguments: { + actions: [ + { + kind: 'uniswap_v3_exact_input_single', + router: ROUTER, + tokenIn: WETH, + tokenOut: USDC, + fee: 3000, + recipient: '0x1234000000000000000000000000000000000000', + amountInWei: '1', + amountOutMinWei: '0', + }, + ], + }, + }, + ]; + + const signals = [ + { + kind: 'priceTrigger', + triggerId: 't1', + priority: 1, + pool: POOL, + poolFee: 3000, + baseToken: WETH, + quoteToken: USDC, + }, + { + kind: 'erc20BalanceSnapshot', + asset: WETH, + amount: '30000', + }, + ]; + + const ok = await validateToolCalls({ + toolCalls, + signals, + commitmentText: 'x', + commitmentSafe: '0x1234000000000000000000000000000000000000', + publicClient: { + getChainId: async () => 11155111, + simulateContract: async ({ address }) => { + assert.equal(address.toLowerCase(), QUOTER.toLowerCase()); + return { result: [1000000n, 0n, 0, 0n] }; + }, + }, + config: {}, + }); + assert.equal(ok.length, 1); + assert.equal(ok[0].parsedArguments.actions[0].amountInWei, '30000'); + assert.equal(ok[0].parsedArguments.actions[0].amountOutMinWei, '995000'); + + const noSnapshot = await validateToolCalls({ + toolCalls, + signals: [signals[0]], + commitmentText: 'x-fallback', + commitmentSafe: '0x1234000000000000000000000000000000000000', + publicClient: { + getChainId: async () => 11155111, + readContract: async ({ functionName, address }) => { + assert.equal(functionName, 'balanceOf'); + assert.equal(address.toLowerCase(), WETH); + return 30000n; + }, + simulateContract: async ({ address }) => { + assert.equal(address.toLowerCase(), QUOTER.toLowerCase()); + return { result: [1000000n, 0n, 0, 0n] }; + }, + }, + config: {}, + }); + assert.equal(noSnapshot.length, 1); + assert.equal(noSnapshot[0].parsedArguments.actions[0].amountInWei, '30000'); + + const rewritten = await validateToolCalls({ + toolCalls: [ + { + ...toolCalls[0], + parsedArguments: { + actions: [ + { + ...toolCalls[0].parsedArguments.actions[0], + router: '0x0000000000000000000000000000000000000001', + }, + ], + }, + }, + ], + signals, + commitmentText: 'y', + commitmentSafe: '0x1234000000000000000000000000000000000000', + publicClient: { + getChainId: async () => 11155111, + simulateContract: async ({ address }) => { + assert.equal(address.toLowerCase(), QUOTER.toLowerCase()); + return { result: [500000n, 0n, 0, 0n] }; + }, + }, + config: {}, + }); + assert.equal(rewritten.length, 1); + assert.equal(rewritten[0].parsedArguments.actions[0].router, ROUTER); + assert.equal(rewritten[0].parsedArguments.actions[0].amountOutMinWei, '497500'); + assert.equal(rewritten[0].parsedArguments.actions[0].operation, 0); + + await assert.rejects( + () => + validateToolCalls({ + toolCalls: [ + { + ...toolCalls[0], + parsedArguments: { + actions: [ + { + ...toolCalls[0].parsedArguments.actions[0], + operation: 1, + }, + ], + }, + }, + ], + signals, + commitmentText: 'op-check', + commitmentSafe: '0x1234000000000000000000000000000000000000', + publicClient: { + getChainId: async () => 11155111, + simulateContract: async () => ({ result: [1000000n, 0n, 0, 0n] }), + }, + config: {}, + }), + /operation must be 0/ + ); + + await assert.rejects( + () => + validateToolCalls({ + toolCalls: [ + { + ...toolCalls[0], + parsedArguments: { + actions: [ + { + ...toolCalls[0].parsedArguments.actions[0], + tokenOut: UNI, + fee: 500, + }, + ], + }, + }, + ], + signals: [ + ...signals, + { + kind: 'priceTrigger', + triggerId: 't2', + priority: 2, + pool: '0x287b0e934ed0439e2a7b1d5f0fc25ea2c24b64f7', + poolFee: 500, + baseToken: UNI, + quoteToken: WETH, + }, + ], + commitmentText: 'winner-check', + commitmentSafe: '0x1234000000000000000000000000000000000000', + publicClient: { + getChainId: async () => 11155111, + simulateContract: async () => ({ result: [1000000n, 0n, 0, 0n] }), + }, + config: {}, + }), + /must match the winning priceTrigger/ + ); + + await onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { + status: 'submitted', + submissionError: { message: 'failed' }, + }, + }); + assert.equal(getSingleFireState().proposalSubmitted, false); + + await onToolOutput({ + name: 'post_bond_and_propose', + parsedOutput: { + status: 'submitted', + proposalHash: '0x1234000000000000000000000000000000000000000000000000000000000000', + }, + }); + + await assert.rejects( + () => + validateToolCalls({ + toolCalls, + signals, + commitmentText: 'z', + commitmentSafe: '0x1234000000000000000000000000000000000000', + publicClient: { + getChainId: async () => 11155111, + simulateContract: async () => ({ result: [1000000n, 0n, 0, 0n] }), + }, + config: {}, + }), + /Single-fire lock engaged/ + ); + + resetSingleFireState(); + process.env.OG_MODULE = '0x1234000000000000000000000000000000000000'; + await reconcileProposalSubmission({ + publicClient: { + getBlockNumber: async () => 100n, + getLogs: async () => [ + { + args: { + proposalHash: + '0xabcd000000000000000000000000000000000000000000000000000000000000', + }, + }, + ], + }, + }); + assert.equal(getSingleFireState().proposalSubmitted, true); + + resetSingleFireState(); + onProposalEvents({ executedProposalCount: 1 }); + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.equal(getSingleFireState().proposalSubmitted, true); + + console.log('[test] allowlist validation OK'); +} + +run().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/agent-library/agents/price-race-swap/test-price-race-swap-agent.mjs b/agent-library/agents/price-race-swap/test-price-race-swap-agent.mjs new file mode 100644 index 00000000..57e9bbd2 --- /dev/null +++ b/agent-library/agents/price-race-swap/test-price-race-swap-agent.mjs @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { getSystemPrompt } from './agent.js'; + +function run() { + const commitmentText = readFileSync(new URL('./commitment.txt', import.meta.url), 'utf8'); + + const prompt = getSystemPrompt({ + proposeEnabled: true, + disputeEnabled: true, + commitmentText, + }); + + assert.ok(prompt.includes('First trigger wins')); + assert.ok(prompt.includes('Use all currently available WETH in the Safe')); + assert.ok(prompt.includes('Do not depend on rigid text pattern matching')); + assert.ok(prompt.includes('Commitment text')); + + console.log('[test] price-race-swap prompt OK'); +} + +run(); diff --git a/agent-library/agents/price-race-swap/test-trigger-inference.mjs b/agent-library/agents/price-race-swap/test-trigger-inference.mjs new file mode 100644 index 00000000..4ed623bc --- /dev/null +++ b/agent-library/agents/price-race-swap/test-trigger-inference.mjs @@ -0,0 +1,69 @@ +import assert from 'node:assert/strict'; +import { sanitizeInferredTriggers } from './agent.js'; + +const WETH = '0x7b79995e5f793a07bc00c21412e50ecae098e7f9'; +const USDC = '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238'; +const UNI = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; +const POOL = '0x6418eec70f50913ff0d756b48d32ce7c02b47c47'; + +function run() { + const normalized = sanitizeInferredTriggers([ + { + id: 'second', + baseToken: UNI, + quoteToken: WETH, + comparator: 'lte', + threshold: 0.03, + priority: 1, + }, + { + id: 'first', + baseToken: WETH, + quoteToken: USDC, + comparator: 'gte', + threshold: 1800, + priority: 0, + pool: POOL, + }, + ]); + + assert.equal(normalized.length, 2); + assert.equal(normalized[0].id, 'first'); + assert.equal(normalized[1].id, 'second'); + assert.equal(normalized[1].poolSelection, 'high-liquidity'); + + assert.throws(() => + sanitizeInferredTriggers([ + { + id: 'dup', + baseToken: WETH, + quoteToken: USDC, + comparator: 'gte', + threshold: 1, + }, + { + id: 'dup', + baseToken: UNI, + quoteToken: WETH, + comparator: 'lte', + threshold: 1, + }, + ]) + ); + + assert.throws(() => + sanitizeInferredTriggers([ + { + id: 'bad', + baseToken: WETH, + quoteToken: WETH, + comparator: 'gte', + threshold: 1, + }, + ]) + ); + + console.log('[test] local inferred trigger sanitizer OK'); +} + +run(); diff --git a/agent-library/agents/timelock-withdraw/agent.js b/agent-library/agents/timelock-withdraw/agent.js new file mode 100644 index 00000000..4d399ac5 --- /dev/null +++ b/agent-library/agents/timelock-withdraw/agent.js @@ -0,0 +1,27 @@ +function getSystemPrompt({ proposeEnabled, disputeEnabled, commitmentText }) { + const mode = proposeEnabled && disputeEnabled + ? 'You may propose and dispute.' + : proposeEnabled + ? 'You may propose but you may not dispute.' + : disputeEnabled + ? 'You may dispute but you may not propose.' + : 'You may not propose or dispute; provide opinions only.'; + + return [ + 'You are a timelock withdrawal agent.', + 'You may only withdraw funds to your own agentAddress, and only after the timelock described in the commitment/rules.', + 'If a timelock trigger fires, re-check the rules and propose withdrawals that follow them.', + 'Never propose withdrawals before the timelock or to any address other than agentAddress.', + 'Default to disputing proposals that violate the rules; prefer no-op when unsure.', + mode, + commitmentText ? `Commitment text:\n${commitmentText}` : '', + 'If an onchain action is needed, call a tool.', + 'Use build_og_transactions to construct proposal payloads, then post_bond_and_propose.', + 'Use dispute_assertion with a short human-readable explanation when disputing.', + 'If no action is needed, output strict JSON with keys: action (propose|deposit|dispute|ignore|other) and rationale (string).', + ] + .filter(Boolean) + .join(' '); +} + +export { getSystemPrompt }; diff --git a/agent-library/agents/timelock-withdraw/agent.json b/agent-library/agents/timelock-withdraw/agent.json new file mode 100644 index 00000000..ba64eceb --- /dev/null +++ b/agent-library/agents/timelock-withdraw/agent.json @@ -0,0 +1,18 @@ +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "Oya Timelock Withdraw Agent", + "description": "Timelock agent that only withdraws funds to its own address after the commitment’s timelock conditions are satisfied.", + "image": "https://raw.githubusercontent.com/oya-commitments/oya-commitments/main/agent-library/agents/timelock-withdraw/agent.png", + "endpoints": [ + { + "name": "agentWallet", + "endpoint": "eip155:11155111:0x2967c076182f0303037072670e744e26ed4a830f" + } + ], + "registrations": [ + { + "agentId": 903, + "agentRegistry": "eip155:11155111:0x8004a818bfb912233c491871b3d84c89a494bd9e" + } + ] +} diff --git a/agent-library/agents/timelock-withdraw/commitment.txt b/agent-library/agents/timelock-withdraw/commitment.txt new file mode 100644 index 00000000..c98288a6 --- /dev/null +++ b/agent-library/agents/timelock-withdraw/commitment.txt @@ -0,0 +1 @@ +Funds may be withdrawn by the agent to the agent's own address one minute after deposit. Before that time, no withdrawals are permitted. diff --git a/agent-library/agents/timelock-withdraw/simulate-timelock.mjs b/agent-library/agents/timelock-withdraw/simulate-timelock.mjs new file mode 100644 index 00000000..8b443641 --- /dev/null +++ b/agent-library/agents/timelock-withdraw/simulate-timelock.mjs @@ -0,0 +1,28 @@ +import { extractTimelockTriggers } from '../../../agent/src/lib/timelock.js'; + +function simulate({ rulesText, depositTimestampMs, nowMs }) { + const deposits = [ + { + id: 'dep1', + timestampMs: depositTimestampMs, + }, + ]; + + const triggers = extractTimelockTriggers({ rulesText, deposits }); + const due = triggers.filter((trigger) => trigger.timestampMs <= nowMs); + + console.log('[sim] rules:', rulesText); + console.log('[sim] depositTimestampMs:', depositTimestampMs); + console.log('[sim] nowMs:', nowMs); + console.log('[sim] triggers:', triggers); + console.log('[sim] due:', due); +} + +const rulesText = + process.env.TIMELOCK_RULES ?? + 'Funds may be withdrawn five minutes after deposit.'; + +const nowMs = Date.now(); +const depositTimestampMs = nowMs - 10 * 60 * 1000; + +simulate({ rulesText, depositTimestampMs, nowMs }); diff --git a/agent-library/agents/timelock-withdraw/test-timelock.mjs b/agent-library/agents/timelock-withdraw/test-timelock.mjs new file mode 100644 index 00000000..7de29d2c --- /dev/null +++ b/agent-library/agents/timelock-withdraw/test-timelock.mjs @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; +import { extractTimelockTriggers } from '../../../agent/src/lib/timelock.js'; + +function run() { + const rulesAbsolute = + 'Funds may be withdrawn after January 15, 2026 12:00AM PST.'; + const absTriggers = extractTimelockTriggers({ rulesText: rulesAbsolute, deposits: [] }); + assert.equal(absTriggers.length, 1); + assert.equal(absTriggers[0].kind, 'absolute'); + assert.ok(absTriggers[0].timestampMs > 0); + + const rulesRelative = + 'Funds may be withdrawn five minutes after deposit.'; + const deposits = [ + { + id: 'dep1', + timestampMs: Date.UTC(2025, 0, 1, 0, 0, 0), + }, + ]; + const relTriggers = extractTimelockTriggers({ rulesText: rulesRelative, deposits }); + assert.equal(relTriggers.length, 1); + assert.equal(relTriggers[0].kind, 'relative'); + assert.equal(relTriggers[0].timestampMs, deposits[0].timestampMs + 5 * 60 * 1000); + + console.log('[test] timelock parsing OK'); +} + +run(); diff --git a/agent-library/package-lock.json b/agent-library/package-lock.json new file mode 100644 index 00000000..6e8a00cc --- /dev/null +++ b/agent-library/package-lock.json @@ -0,0 +1,216 @@ +{ + "name": "agent-library", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "viem": "^2.20.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/ox": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", + "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem": { + "version": "2.45.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.45.2.tgz", + "integrity": "sha512-GXPMmj0ukqFNL87sgpsZBy4CjGvsFQk42/EUdsn8dv3ZWtL4ukDXNCM0nME2hU0IcuS29CuUbrwbZN6iWxAipw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.11.3", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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 + } + } + } + } +} diff --git a/agent-library/package.json b/agent-library/package.json new file mode 100644 index 00000000..ea631776 --- /dev/null +++ b/agent-library/package.json @@ -0,0 +1 @@ +{"type":"module","dependencies":{"viem":"^2.20.0"}} diff --git a/agent/.env.example b/agent/.env.example new file mode 100644 index 00000000..732983a5 --- /dev/null +++ b/agent/.env.example @@ -0,0 +1,89 @@ +# Required +RPC_URL=https://... +COMMITMENT_SAFE=0x... +OG_MODULE=0x... +WATCH_ASSETS=0xToken1,0xToken2 + +# Signer selection (default: env) +SIGNER_TYPE=env +PRIVATE_KEY=0x... + +# Encrypted keystore +# SIGNER_TYPE=keystore +# KEYSTORE_PATH=./keys/agent.json +# KEYSTORE_PASSWORD=... + +# OS keychain (macOS Keychain or Linux Secret Service) +# SIGNER_TYPE=keychain +# KEYCHAIN_SERVICE=og-deployer +# KEYCHAIN_ACCOUNT=agent + +# Vault KV (private key stored as a secret) +# SIGNER_TYPE=vault +# VAULT_ADDR=https://vault.example.com +# VAULT_TOKEN=... +# VAULT_SECRET_PATH=secret/data/og-deployer +# VAULT_SECRET_KEY=private_key + +# KMS/Vault signer via RPC (eth_sendTransaction) +# SIGNER_TYPE=rpc +# SIGNER_RPC_URL=https://signer.example.com +# SIGNER_ADDRESS=0x... + +# Optional tuning +POLL_INTERVAL_MS=60000 +WATCH_NATIVE_BALANCE=true +# Optional Polymarket config +# POLYMARKET_CONDITIONAL_TOKENS=0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 +# POLYMARKET_EXCHANGE= +# POLYMARKET_CLOB_ENABLED=false +# POLYMARKET_CLOB_HOST=https://clob.polymarket.com +# POLYMARKET_CLOB_ADDRESS= +# POLYMARKET_CLOB_SIGNATURE_TYPE=POLY_GNOSIS_SAFE +# POLYMARKET_CLOB_API_KEY= +# POLYMARKET_CLOB_API_SECRET= +# POLYMARKET_CLOB_API_PASSPHRASE= +# POLYMARKET_CLOB_REQUEST_TIMEOUT_MS=15000 +# POLYMARKET_CLOB_MAX_RETRIES=1 +# POLYMARKET_CLOB_RETRY_DELAY_MS=250 +# Optional Polymarket relayer (gasless SAFE/PROXY transactions) +# POLYMARKET_RELAYER_ENABLED=false +# POLYMARKET_RELAYER_HOST=https://relayer-v2.polymarket.com +# POLYMARKET_RELAYER_TX_TYPE=SAFE +# POLYMARKET_RELAYER_FROM_ADDRESS= +# POLYMARKET_RELAYER_SAFE_FACTORY= +# POLYMARKET_RELAYER_PROXY_FACTORY= +# POLYMARKET_RELAYER_RESOLVE_PROXY_ADDRESS=true +# POLYMARKET_RELAYER_AUTO_DEPLOY_PROXY=false +# POLYMARKET_RELAYER_CHAIN_ID= +# POLYMARKET_RELAYER_REQUEST_TIMEOUT_MS=15000 +# POLYMARKET_RELAYER_POLL_INTERVAL_MS=2000 +# POLYMARKET_RELAYER_POLL_TIMEOUT_MS=120000 +# POLYMARKET_API_KEY= +# POLYMARKET_API_SECRET= +# POLYMARKET_API_PASSPHRASE= +# POLYMARKET_BUILDER_API_KEY= +# POLYMARKET_BUILDER_SECRET= +# POLYMARKET_BUILDER_PASSPHRASE= +# Optional Uniswap config overrides (otherwise chain defaults are used) +# UNISWAP_V3_FACTORY= +# UNISWAP_V3_QUOTER= +# UNISWAP_V3_FEE_TIERS=500,3000,10000 +# PROPOSE_ENABLED=true +# ALLOW_PROPOSE_ON_SIMULATION_FAIL=false +# DISPUTE_ENABLED=true +# START_BLOCK= +# DEFAULT_DEPOSIT_ASSET= +# DEFAULT_DEPOSIT_AMOUNT_WEI= +# AGENT_MODULE=default + +# ERC-8004 registration helpers +# AGENT_ORG=oyaprotocol +# AGENT_REPO=oya-commitments +# AGENT_BRANCH=erc-8004 +# AGENT_NETWORK=ethereum-sepolia + +# Optional LLM +# OPENAI_API_KEY= +# OPENAI_MODEL=gpt-4.1-mini +# OPENAI_BASE_URL=https://api.openai.com/v1 diff --git a/agent/AGENTS.md b/agent/AGENTS.md new file mode 100644 index 00000000..adc55d53 --- /dev/null +++ b/agent/AGENTS.md @@ -0,0 +1,24 @@ +# Agent Runner Guidelines + +## Scope + +This file applies to `agent/`. + +## Purpose + +`agent/` contains shared offchain runner infrastructure (config, signer handling, polling, transaction helpers, and runtime wiring). + +## Rules + +- Keep this directory generalized and reusable across multiple agent modules. +- Do not add behavior that is only relevant to one agent module. +- If a shared runner change is required, preserve backward compatibility for existing modules where practical. + +## Locality Rule + +If behavior is specific to one agent, implement it in `agent-library/agents//` instead of `agent/`. + +## Validation + +- Run relevant Node checks and scripts for changed code paths. +- Run at least one affected module test/simulation from `agent-library/agents//`. diff --git a/agent/README.md b/agent/README.md new file mode 100644 index 00000000..a66cc02a --- /dev/null +++ b/agent/README.md @@ -0,0 +1,327 @@ +# Oya Commitment Agent + +Generic offchain agent wiring for monitoring an Oya commitment and acting through the Optimistic Governor. It exposes only the core tools needed to serve commitments; add commitment-specific logic, prompts, and extra tools as needed. + +## Beta Disclaimer + +This is beta software provided “as is.” Use at your own risk. No guarantees of safety, correctness, or fitness for any purpose. + +## Prerequisites + +- Node.js 18+ +- RPC endpoint the agent can reach +- Private key funded for gas and bond currency to propose through the Optimistic Governor + +## Configure + +1. Copy `.env.example` to `.env` and fill in: + - `RPC_URL`: RPC the agent should use + - `COMMITMENT_SAFE`: Safe address holding assets + - `OG_MODULE`: Optimistic Governor module address + - `WATCH_ASSETS`: Comma-separated ERC20s to monitor (the OG collateral is auto-added) + - Signer selection: `SIGNER_TYPE` (default `env`) + - `env`: `PRIVATE_KEY` + - `keystore`: `KEYSTORE_PATH`, `KEYSTORE_PASSWORD` + - `keychain`: `KEYCHAIN_SERVICE`, `KEYCHAIN_ACCOUNT` (macOS Keychain or Linux Secret Service) + - `vault`: `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_SECRET_PATH`, optional `VAULT_SECRET_KEY` (default `private_key`) + - `kms`/`vault-signer`/`rpc`: `SIGNER_RPC_URL`, `SIGNER_ADDRESS` (JSON-RPC signer that accepts `eth_sendTransaction`) + - Optional tuning: `POLL_INTERVAL_MS`, `START_BLOCK`, `WATCH_NATIVE_BALANCE`, `DEFAULT_DEPOSIT_*`, `AGENT_MODULE`, `UNISWAP_V3_FACTORY`, `UNISWAP_V3_QUOTER`, `UNISWAP_V3_FEE_TIERS`, `POLYMARKET_*` + - Optional proposals: `PROPOSE_ENABLED` (default true), `ALLOW_PROPOSE_ON_SIMULATION_FAIL` (default false) + - Optional disputes: `DISPUTE_ENABLED` (default true), `DISPUTE_RETRY_MS` (default 60000) + - Optional LLM: `OPENAI_API_KEY`, `OPENAI_MODEL` (default `gpt-4.1-mini`), `OPENAI_BASE_URL` +2. Install deps and start the loop: + +```bash +npm install +npm start +``` + +## Alternative Signing Methods for Forge Scripts + +You can reuse the agent’s signer helpers to inject a private key env var for Forge scripts without storing raw keys in `.env`. + +```shell +# Private key from env +SIGNER_TYPE=env PRIVATE_KEY=0x... \ + node agent/with-signer.mjs --env DEPLOYER_PK -- \ + forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast + +# Encrypted keystore +SIGNER_TYPE=keystore KEYSTORE_PATH=./keys/deployer.json KEYSTORE_PASSWORD=... \ + node agent/with-signer.mjs --env DEPLOYER_PK -- \ + forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast +``` + +For interactions, swap the env var (e.g., `PROPOSER_PK`, `EXECUTOR_PK`). For signing without exporting a key, use an RPC signer proxy (`SIGNER_RPC_URL`, `SIGNER_ADDRESS`) that supports `eth_sendTransaction`. + +## What the Agent Does + +- **Polls for deposits**: Checks ERC20 `Transfer` logs into the commitment and (optionally) native balance increases. If nothing changed, no LLM/decision code runs. +- **Bonds + proposes**: `postBondAndPropose` approves the OG collateral bond and calls `proposeTransactions` on the module. +- **Monitors proposals**: Watches for Optimistic Governor proposals and routes them to the LLM for rule checks. +- **Disputes assertions**: When the LLM flags a proposal as violating the rules, the agent posts the Oracle V3 bond and disputes the associated assertion. A human-readable rationale is logged locally. +- **Deposits**: `makeDeposit` can send ERC20 or native assets into the commitment. +- **Optional LLM decisions**: If `OPENAI_API_KEY` is set, the runner will call the OpenAI Responses API with signals and OG context and expect strict-JSON actions (propose/deposit/ignore). Wire your own validation/broadcast of any suggested actions in the agent module. +- **Timelock triggers**: Parses plain language timelocks in rules (absolute dates or “X minutes after deposit”) and emits `timelock` signals when due. +- **Price triggers**: If a module exports `getPriceTriggers({ commitmentText, config })`, the runner evaluates those parsed/inferred Uniswap V3 thresholds and emits `priceTrigger` signals. + +All other behavior is intentionally left out. Implement your own agent in `agent-library/agents//agent.js` to add commitment-specific logic and tool use. + +### Price Trigger Config + +Export `getPriceTriggers({ commitmentText, config })` from `agent-library/agents//agent.js` when your agent needs price-trigger behavior. This keeps commitment interpretation local to the module. + +### Uniswap Swap Action in `build_og_transactions` + +`build_og_transactions` supports action kind `uniswap_v3_exact_input_single`, which expands to: +1. ERC20 `approve(tokenIn -> router, amountInWei)` +2. Router `exactInputSingle(...)` + +This lets agents propose reusable Uniswap swap calldata without embedding raw ABI in prompts. + +### Polymarket Support (CLOB + CTF) + +The shared tooling supports: +- Onchain Conditional Tokens Framework (CTF) actions through `build_og_transactions`. +- Offchain CLOB order placement/cancel through signed API requests. +- Direct ERC1155 deposits to the commitment Safe. + +#### Polymarket Environment Variables + +Set these when using Polymarket functionality: +- `POLYMARKET_CONDITIONAL_TOKENS`: Optional CTF contract address override used by CTF actions (default is Polymarket mainnet ConditionalTokens). +- `POLYMARKET_EXCHANGE`: Optional CTF exchange override for EIP-712 order signing domain. +- `POLYMARKET_CLOB_ENABLED`: Enable CLOB tools (`true`/`false`, default `false`). +- `POLYMARKET_CLOB_HOST`: CLOB API host (default `https://clob.polymarket.com`). +- `POLYMARKET_CLOB_ADDRESS`: Optional address used as `POLY_ADDRESS` for CLOB auth (for proxy/funder setups). Defaults to runtime signer address. +- `POLYMARKET_CLOB_SIGNATURE_TYPE`: Optional default order signature type for build/sign flow (`EOA`/`POLY_PROXY`/`POLY_GNOSIS_SAFE` or `0`/`1`/`2`). + - Per Polymarket docs: `0=EOA`, `1=POLY_PROXY`, `2=POLY_GNOSIS_SAFE`. + - When using `POLY_PROXY` or `POLY_GNOSIS_SAFE`, set `POLYMARKET_CLOB_ADDRESS` to the proxy/funder wallet address. +- `POLYMARKET_CLOB_API_KEY`, `POLYMARKET_CLOB_API_SECRET`, `POLYMARKET_CLOB_API_PASSPHRASE`: Required for authenticated CLOB calls. +- `POLYMARKET_CLOB_REQUEST_TIMEOUT_MS`, `POLYMARKET_CLOB_MAX_RETRIES`, `POLYMARKET_CLOB_RETRY_DELAY_MS`: Optional request tuning. +- `POLYMARKET_RELAYER_ENABLED`: Enable Polymarket relayer submission for ERC1155 deposits (`true`/`false`, default `false`). +- `POLYMARKET_RELAYER_HOST`: Relayer API host (default `https://relayer-v2.polymarket.com`). +- `POLYMARKET_RELAYER_TX_TYPE`: Relayer wallet type (`SAFE` default, or `PROXY`). +- `POLYMARKET_RELAYER_FROM_ADDRESS`: Optional explicit relayer proxy wallet address (if omitted, runtime auto-resolves from signer + relayer APIs / deterministic address). +- `POLYMARKET_RELAYER_SAFE_FACTORY`, `POLYMARKET_RELAYER_PROXY_FACTORY`: Optional factory overrides for deterministic SAFE/PROXY address derivation. +- `POLYMARKET_RELAYER_RESOLVE_PROXY_ADDRESS`: Resolve proxy address via relayer API when from-address is not set (default `true`). +- `POLYMARKET_RELAYER_AUTO_DEPLOY_PROXY`: Optionally create proxy wallet when absent (default `false`). +- `POLYMARKET_RELAYER_CHAIN_ID`, `POLYMARKET_RELAYER_REQUEST_TIMEOUT_MS`, `POLYMARKET_RELAYER_POLL_INTERVAL_MS`, `POLYMARKET_RELAYER_POLL_TIMEOUT_MS`: Optional relayer runtime tuning. +- Builder credentials for relayer auth headers: + - Preferred: `POLYMARKET_BUILDER_API_KEY`, `POLYMARKET_BUILDER_SECRET`, `POLYMARKET_BUILDER_PASSPHRASE`. + - Fallbacks supported: `POLYMARKET_API_*` then `POLYMARKET_CLOB_API_*`. + +#### Execution Modes + +- `PROPOSE_ENABLED=true` and/or `DISPUTE_ENABLED=true`: onchain tools are enabled (`build_og_transactions`, `make_deposit`, `make_erc1155_deposit`, propose/dispute tools). +- `PROPOSE_ENABLED=false` and `DISPUTE_ENABLED=false`: onchain tools are disabled. +- `POLYMARKET_CLOB_ENABLED=true`: CLOB tools can still run in this mode (`polymarket_clob_place_order`, `polymarket_clob_build_sign_and_place_order`, `polymarket_clob_cancel_orders`). +- All three disabled (`PROPOSE_ENABLED=false`, `DISPUTE_ENABLED=false`, `POLYMARKET_CLOB_ENABLED=false`): monitor/opinion only. + +#### CTF Actions (`build_og_transactions`) + +Supported kinds: +- `ctf_split` +- `ctf_merge` +- `ctf_redeem` + +Example `ctf_split` action: + +```json +{ + "name": "build_og_transactions", + "arguments": { + "actions": [ + { + "kind": "ctf_split", + "collateralToken": "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "conditionId": "0x1111111111111111111111111111111111111111111111111111111111111111", + "partition": [1, 2], + "amount": "1000000" + } + ] + } +} +``` + +`ctf_split` auto-inserts ERC20 approvals to the CTF contract (`approve(0)`, then `approve(amount)`) before `splitPosition(...)`. + +#### ERC1155 Deposit to Safe + +Use `make_erc1155_deposit` after receiving YES/NO position tokens: + +```json +{ + "name": "make_erc1155_deposit", + "arguments": { + "token": "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045", + "tokenId": "123456789", + "amount": "1", + "data": "0x" + } +} +``` + +When `POLYMARKET_RELAYER_ENABLED=true`, this tool submits via Polymarket relayer (SAFE/PROXY) instead of direct onchain `writeContract`. If `POLYMARKET_RELAYER_FROM_ADDRESS` is not set, the runtime resolves the proxy wallet from the signer and relayer metadata. For SAFE mode, any explicitly configured proxy wallet must match the relayer-derived SAFE address for that signer. + +#### CLOB Place/Cancel Tools + +`polymarket_clob_place_order` submits a pre-signed order: + +```json +{ + "name": "polymarket_clob_place_order", + "arguments": { + "side": "BUY", + "tokenId": "123456789", + "orderType": "GTC", + "signedOrder": { + "maker": "0xYourSignerOrClobAddress", + "tokenId": "123456789", + "side": "BUY" + } + } +} +``` + +`polymarket_clob_build_sign_and_place_order` builds and signs the order with the runtime signer before submission: + +```json +{ + "name": "polymarket_clob_build_sign_and_place_order", + "arguments": { + "side": "BUY", + "tokenId": "123456789", + "orderType": "FOK", + "makerAmount": "1000000", + "takerAmount": "450000" + } +} +``` + +`polymarket_clob_cancel_orders` supports `ids`, `market`, or `all`: + +```json +{ + "name": "polymarket_clob_cancel_orders", + "arguments": { + "mode": "ids", + "orderIds": ["order-id-1"] + } +} +``` + +#### CLOB Identity Validation Rules + +For `polymarket_clob_place_order`, the runner validates the same order payload that will be sent to `/order`: +- The submitted order must include `side` and `tokenId`/`asset_id` that match declared tool args. +- The submitted order must include at least one identity field: `maker`/`signer`/`funder`/`user` (or corresponding `*Address` variants). +- Every extracted identity address must be allowlisted: + - runtime signer address, and + - `POLYMARKET_CLOB_ADDRESS` when set. + +If any identity is outside that allowlist, the tool call is rejected before submission. + +For `polymarket_clob_build_sign_and_place_order`, `maker` and `signer` must also be one of: +- runtime signer address, or +- `POLYMARKET_CLOB_ADDRESS` when set. + +This tool requires a signer backend that supports `signTypedData`. + +#### CLOB Retry Behavior + +- `POST /order` is not automatically retried. +- Cancel endpoints (and other retry-eligible requests) can use configured retry settings. + +### Propose vs Dispute Modes + +Set `PROPOSE_ENABLED` and `DISPUTE_ENABLED` to control behavior: +- Both true: propose and dispute as needed (default). +- Only `PROPOSE_ENABLED=true`: propose only, never dispute. +- Only `DISPUTE_ENABLED=true`: dispute only, never propose. +- Both false: monitor and log opinions only; no on-chain actions. + +### Agent Modules & Commitments + +Use `AGENT_MODULE` to point to an agent implementation name (e.g., `default`, `timelock-withdraw`). The runner will load `agent-library/agents//agent.js`. +Each agent directory must include a `commitment.txt` with the plain language commitment the agent is designed to serve. + +You can validate a module quickly: + +```bash +node agent/scripts/validate-agent.mjs --module=default +``` + +Default agent smoke test: + +```bash +node agent-library/agents/default/test-default-agent.mjs +``` + +Update ERC-8004 metadata after registration: + +```bash +AGENT_ID=1 AGENT_WALLET=0x... \ +node agent/scripts/update-agent-metadata.mjs --agent=default +``` + +Register an agent on Sepolia (and update metadata in-place): + +```bash +AGENT_MODULE=default \ +AGENT_BRANCH= \ +AGENT_NETWORK=ethereum-sepolia \ +node agent/scripts/register-erc8004.mjs +``` + +The script infers `AGENT_URI` as: +`https://raw.githubusercontent.com////agent-library/agents//agent.json` +Defaults: `AGENT_ORG=oyaprotocol`, `AGENT_REPO=oya-commitments` +Override with `AGENT_URI` or `AGENT_URI_BASE` if needed. + +### Timelock Agent Testing + +Unit test (plain JS): + +```bash +node agent-library/agents/timelock-withdraw/test-timelock.mjs +``` + +Simulation (prints due triggers): + +```bash +node agent-library/agents/timelock-withdraw/simulate-timelock.mjs +``` + +Run the timelock agent: + +```bash +AGENT_MODULE=timelock-withdraw \ +node agent/src/index.js +``` + +## Local Dispute Simulation + +Use this to validate the dispute path against local mock contracts. + +```bash +# 1) Start anvil in another terminal +anvil + +# 2) Build the Solidity artifacts (includes mock OO/OG/ERC20) +forge build + +# 3) Run the no-dispute case (assertion remains undisputed) +RPC_URL=http://127.0.0.1:8545 \ +PRIVATE_KEY= \ +node agent/scripts/simulate-dispute.mjs --case=no-dispute + +# 4) Run the dispute case (assertion disputed, bond transferred) +RPC_URL=http://127.0.0.1:8545 \ +PRIVATE_KEY= \ +node agent/scripts/simulate-dispute.mjs --case=dispute +``` diff --git a/agent/package-lock.json b/agent/package-lock.json new file mode 100644 index 00000000..7b267484 --- /dev/null +++ b/agent/package-lock.json @@ -0,0 +1,339 @@ +{ + "name": "og-commitment-agent", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "og-commitment-agent", + "version": "0.1.0", + "dependencies": { + "dotenv": "^16.4.5", + "ethers": "^6.12.0", + "viem": "^2.20.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ethers": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "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 + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/ox": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", + "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.45.1", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.45.1.tgz", + "integrity": "sha512-LN6Pp7vSfv50LgwhkfSbIXftAM5J89lP9x8TeDa8QM7o41IxlHrDh0F9X+FfnCWtsz11pEVV5sn+yBUoOHNqYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.11.3", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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 + } + } + } + } +} diff --git a/agent/package.json b/agent/package.json new file mode 100644 index 00000000..78a95352 --- /dev/null +++ b/agent/package.json @@ -0,0 +1,14 @@ +{ + "name": "og-commitment-agent", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start": "node ./src/index.js" + }, + "dependencies": { + "dotenv": "^16.4.5", + "ethers": "^6.12.0", + "viem": "^2.20.0" + } +} diff --git a/agent/scripts/register-erc8004.mjs b/agent/scripts/register-erc8004.mjs new file mode 100644 index 00000000..3c9443ae --- /dev/null +++ b/agent/scripts/register-erc8004.mjs @@ -0,0 +1,253 @@ +import dotenv from 'dotenv'; +import { readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createPublicClient, createWalletClient, decodeEventLog, http } from 'viem'; +import { getAddress } from 'viem'; +import { createSignerClient } from '../src/lib/signer.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '../..'); + +dotenv.config(); +dotenv.config({ path: path.resolve(repoRoot, 'agent/.env') }); + +function getArgValue(prefix) { + const arg = process.argv.find((value) => value.startsWith(prefix)); + return arg ? arg.slice(prefix.length) : null; +} + +function formatCaip10(chainId, address) { + return `eip155:${chainId}:${address.toLowerCase()}`; +} + +function normalizeAgentName(agentRef) { + if (!agentRef) return 'default'; + if (!agentRef.includes('/')) return agentRef; + const trimmed = agentRef.endsWith('.js') ? path.dirname(agentRef) : agentRef; + return path.basename(trimmed); +} + +const REGISTRY_BY_NETWORK = { + ethereum: { + identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', + reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63', + }, + 'ethereum-sepolia': { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, + base: { + identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', + reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63', + }, + 'base-sepolia': { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, + polygon: { + identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', + reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63', + }, + 'polygon-amoy': { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, + gnosis: { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, + scroll: { + identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', + reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63', + }, + 'scroll-testnet': { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, + monad: { + identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', + reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63', + }, + 'monad-testnet': { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, + bsc: { + identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', + reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63', + }, + 'bsc-testnet': { + identityRegistry: '0x8004A818BFB912233c491871b3d84c89A494BD9e', + reputationRegistry: '0x8004B663056A597Dffe9eCcC1965A193B7388713', + }, +}; + +const identityRegistryAbi = [ + { + type: 'function', + name: 'register', + inputs: [{ name: 'agentURI', type: 'string' }], + outputs: [{ name: 'agentId', type: 'uint256' }], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'Registered', + inputs: [ + { indexed: true, name: 'agentId', type: 'uint256' }, + { indexed: false, name: 'agentURI', type: 'string' }, + { indexed: true, name: 'owner', type: 'address' }, + ], + anonymous: false, + }, +]; + +async function main() { + const agentRef = getArgValue('--agent=') ?? process.env.AGENT_MODULE ?? 'default'; + const agentName = normalizeAgentName(agentRef); + const agentDir = agentRef.includes('/') + ? agentRef.endsWith('.js') + ? path.dirname(agentRef) + : agentRef + : `agent-library/agents/${agentName}`; + const agentJsonPath = path.resolve(repoRoot, agentDir, 'agent.json'); + + const agentUriArg = getArgValue('--agent-uri='); + const agentOrg = process.env.AGENT_ORG ?? 'oyaprotocol'; + const agentRepo = process.env.AGENT_REPO ?? 'oya-commitments'; + const agentBranch = process.env.AGENT_BRANCH; + if (!agentBranch && !process.env.AGENT_URI_BASE && !process.env.AGENT_URI) { + throw new Error('Missing AGENT_BRANCH (or provide AGENT_URI / AGENT_URI_BASE).'); + } + const agentUriBase = + process.env.AGENT_URI_BASE ?? + (agentBranch + ? `https://raw.githubusercontent.com/${agentOrg}/${agentRepo}/${agentBranch}/agent-library/agents` + : null); + const agentUri = + agentUriArg ?? + process.env.AGENT_URI ?? + (agentUriBase ? `${agentUriBase}/${agentName}/agent.json` : null); + if (!agentUri) { + throw new Error('Missing --agent-uri or AGENT_URI (or AGENT_URI_BASE).'); + } + + const rpcUrl = process.env.RPC_URL; + if (!rpcUrl) { + throw new Error('Missing RPC_URL.'); + } + + const publicClient = createPublicClient({ transport: http(rpcUrl) }); + const { account, walletClient } = await createSignerClient({ rpcUrl }); + + const chainId = + Number(getArgValue('--chain-id=')) || + Number(process.env.CHAIN_ID) || + (await publicClient.getChainId()); + const registryOverride = getArgValue('--agent-registry=') ?? process.env.AGENT_REGISTRY; + const network = + getArgValue('--network=') ?? + process.env.AGENT_NETWORK ?? + (chainId === 1 + ? 'ethereum' + : chainId === 11155111 + ? 'ethereum-sepolia' + : undefined); + const registry = + registryOverride ?? (network ? REGISTRY_BY_NETWORK[network]?.identityRegistry : undefined); + if (!registry) { + throw new Error( + `No IdentityRegistry configured for chainId ${chainId}. Provide --agent-registry or set AGENT_NETWORK.` + ); + } + + const txHash = await walletClient.writeContract({ + address: getAddress(registry), + abi: identityRegistryAbi, + functionName: 'register', + args: [agentUri], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + let agentId; + for (const log of receipt.logs) { + try { + const decoded = decodeEventLog({ + abi: identityRegistryAbi, + data: log.data, + topics: log.topics, + }); + if (decoded.eventName === 'Registered') { + agentId = decoded.args.agentId; + break; + } + } catch (error) { + // ignore unrelated logs + } + } + + if (agentId === undefined) { + throw new Error('Failed to parse Registered event for agentId.'); + } + + const wallet = getArgValue('--agent-wallet=') ?? process.env.AGENT_WALLET ?? account.address; + const registryEndpoint = formatCaip10(chainId, registry).toLowerCase(); + const walletEndpoint = formatCaip10(chainId, wallet); + + const raw = await readFile(agentJsonPath, 'utf8'); + const json = JSON.parse(raw); + json.endpoints = Array.isArray(json.endpoints) ? json.endpoints : []; + const existingEndpoint = json.endpoints.find((item) => item?.name === 'agentWallet'); + if (existingEndpoint) { + existingEndpoint.endpoint = walletEndpoint; + } else { + json.endpoints.push({ name: 'agentWallet', endpoint: walletEndpoint }); + } + + const agentIdValue = String(agentId); + json.registrations = Array.isArray(json.registrations) ? json.registrations : []; + const normalizedRegistrations = []; + let updated = false; + for (const entry of json.registrations) { + if (!entry?.agentRegistry) continue; + const normalizedRegistry = String(entry.agentRegistry).toLowerCase(); + if (normalizedRegistry === registryEndpoint) { + if (!updated) { + normalizedRegistrations.push({ + agentId: agentIdValue, + agentRegistry: registryEndpoint, + }); + updated = true; + } + continue; + } + normalizedRegistrations.push({ + ...entry, + agentRegistry: normalizedRegistry, + }); + } + if (!updated) { + normalizedRegistrations.push({ + agentId: agentIdValue, + agentRegistry: registryEndpoint, + }); + } + json.registrations = normalizedRegistrations; + + await writeFile(agentJsonPath, `${JSON.stringify(json, null, 2)}\n`, 'utf8'); + + console.log('[agent] Registered:', { + agentId: agentIdValue, + agentRegistry: registryEndpoint, + agentURI: agentUri, + txHash, + }); + console.log('[agent] Updated metadata:', agentJsonPath); +} + +main().catch((error) => { + console.error('[agent] registration failed:', error.message ?? error); + process.exit(1); +}); diff --git a/agent/scripts/simulate-dispute.mjs b/agent/scripts/simulate-dispute.mjs new file mode 100644 index 00000000..283381c3 --- /dev/null +++ b/agent/scripts/simulate-dispute.mjs @@ -0,0 +1,163 @@ +import 'dotenv/config'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + createPublicClient, + createWalletClient, + getAddress, + http, + parseAbi, +} from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '../..'); + +function mustGetEnv(key) { + const value = process.env[key]; + if (!value) { + throw new Error(`Missing required env var ${key}`); + } + return value; +} + +function loadArtifact(relativePath) { + return readFile(path.join(repoRoot, relativePath), 'utf8').then((raw) => JSON.parse(raw)); +} + +async function deployContract({ walletClient, publicClient, abi, bytecode, args }) { + const hash = await walletClient.deployContract({ + abi, + bytecode, + args, + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + if (!receipt.contractAddress) { + throw new Error('Deployment failed (no contractAddress).'); + } + return receipt.contractAddress; +} + +async function main() { + const rpcUrl = mustGetEnv('RPC_URL'); + const privateKey = mustGetEnv('PRIVATE_KEY'); + const caseArg = process.argv.find((arg) => arg.startsWith('--case=')); + const scenario = caseArg ? caseArg.split('=')[1] : 'dispute'; + if (!['dispute', 'no-dispute'].includes(scenario)) { + throw new Error('Case must be one of: dispute, no-dispute.'); + } + + const account = privateKeyToAccount(privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`); + const publicClient = createPublicClient({ transport: http(rpcUrl) }); + const walletClient = createWalletClient({ account, transport: http(rpcUrl) }); + + const [erc20Artifact, ooArtifact, ogArtifact] = await Promise.all([ + loadArtifact('out/MockERC20.sol/MockERC20.json'), + loadArtifact('out/MockOptimisticOracleV3.sol/MockOptimisticOracleV3.json'), + loadArtifact('out/MockOptimisticGovernor.sol/MockOptimisticGovernor.json'), + ]); + + const erc20 = await deployContract({ + walletClient, + publicClient, + abi: erc20Artifact.abi, + bytecode: erc20Artifact.bytecode.object ?? erc20Artifact.bytecode, + args: ['Bond', 'BOND', 18], + }); + + const oo = await deployContract({ + walletClient, + publicClient, + abi: ooArtifact.abi, + bytecode: ooArtifact.bytecode.object ?? ooArtifact.bytecode, + args: [], + }); + + const og = await deployContract({ + walletClient, + publicClient, + abi: ogArtifact.abi, + bytecode: ogArtifact.bytecode.object ?? ogArtifact.bytecode, + args: [ + getAddress(erc20), + 0n, + getAddress(oo), + 'Mock rules', + '0x' + '11'.repeat(32), + 3600, + ], + }); + + const erc20Client = { + address: erc20, + abi: erc20Artifact.abi, + }; + const ooClient = { + address: oo, + abi: ooArtifact.abi, + }; + + const assertionId = `0x${'aa'.repeat(32)}`; + const now = BigInt((await publicClient.getBlock()).timestamp); + + const mintHash = await walletClient.writeContract({ + ...erc20Client, + functionName: 'mint', + args: [account.address, 1_000_000n], + }); + await publicClient.waitForTransactionReceipt({ hash: mintHash }); + + const assertionHash = await walletClient.writeContract({ + ...ooClient, + functionName: 'setAssertionSimple', + args: [ + assertionId, + account.address, + Number(now), + false, + erc20, + Number(now + 3600n), + '0x' + '22'.repeat(32), + 100_000n, + ], + }); + await publicClient.waitForTransactionReceipt({ hash: assertionHash }); + + const seeded = await publicClient.readContract({ + ...ooClient, + functionName: 'getAssertion', + args: [assertionId], + }); + console.log('[sim] Seeded assertion currency:', seeded.currency); + + process.env.COMMITMENT_SAFE = account.address; + process.env.OG_MODULE = og; + process.env.WATCH_ASSETS = erc20; + process.env.OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? ''; + + const { postBondAndDispute } = await import('../src/index.js'); + + if (scenario === 'dispute') { + await postBondAndDispute({ + assertionId, + explanation: 'Simulation dispute: proposal violates rules.', + }); + } else { + console.log('[sim] No-dispute case: leaving assertion undisputed.'); + } + + const updated = await publicClient.readContract({ + ...ooClient, + functionName: 'getAssertion', + args: [assertionId], + }); + + console.log('[sim] Assertion disputer:', updated.disputer); +} + +main().catch((error) => { + console.error('[sim] failed', error); + process.exit(1); +}); diff --git a/agent/scripts/test-build-og-transactions.mjs b/agent/scripts/test-build-og-transactions.mjs new file mode 100644 index 00000000..de34ca03 --- /dev/null +++ b/agent/scripts/test-build-og-transactions.mjs @@ -0,0 +1,125 @@ +import assert from 'node:assert/strict'; +import { decodeFunctionData, erc20Abi, parseAbi } from 'viem'; +import { buildOgTransactions } from '../src/lib/tx.js'; + +function run() { + const router = '0xE592427A0AEce92De3Edee1F18E0157C05861564'; + const usdc = '0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const weth = '0xC02aaA39b223FE8D0A0E5C4F27eAD9083C756Cc2'; + const recipient = '0x1111111111111111111111111111111111111111'; + + const txs = buildOgTransactions([ + { + kind: 'uniswap_v3_exact_input_single', + token: null, + to: null, + amountWei: null, + valueWei: null, + abi: null, + args: null, + operation: 0, + router, + tokenIn: usdc, + tokenOut: weth, + fee: 3000, + recipient, + amountInWei: '1000000', + amountOutMinWei: '1', + sqrtPriceLimitX96: null, + }, + ]); + + assert.equal(txs.length, 2); + assert.equal(txs[0].to.toLowerCase(), usdc.toLowerCase()); + assert.equal(txs[1].to.toLowerCase(), router.toLowerCase()); + const approveCall = decodeFunctionData({ + abi: erc20Abi, + data: txs[0].data, + }); + assert.equal(approveCall.functionName, 'approve'); + + const swapCall = decodeFunctionData({ + abi: parseAbi([ + 'function exactInputSingle((address tokenIn,address tokenOut,uint24 fee,address recipient,uint256 amountIn,uint256 amountOutMinimum,uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)', + ]), + data: txs[1].data, + }); + assert.equal(swapCall.functionName, 'exactInputSingle'); + + const ctf = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'; + const conditionId = `0x${'11'.repeat(32)}`; + const ctfTxs = buildOgTransactions( + [ + { + kind: 'ctf_split', + ctfContract: null, + collateralToken: usdc, + conditionId, + parentCollectionId: null, + partition: [1, 2], + amount: '250000', + operation: 0, + }, + ], + { + config: { + polymarketConditionalTokens: ctf, + }, + } + ); + assert.equal(ctfTxs.length, 3); + assert.equal(ctfTxs[0].to.toLowerCase(), usdc.toLowerCase()); + assert.equal(ctfTxs[1].to.toLowerCase(), usdc.toLowerCase()); + assert.equal(ctfTxs[2].to.toLowerCase(), ctf.toLowerCase()); + assert.equal(ctfTxs[0].operation, 0); + assert.equal(ctfTxs[1].operation, 0); + assert.equal(ctfTxs[2].operation, 0); + + const resetApproveCall = decodeFunctionData({ + abi: erc20Abi, + data: ctfTxs[0].data, + }); + assert.equal(resetApproveCall.functionName, 'approve'); + assert.equal(resetApproveCall.args[0].toLowerCase(), ctf.toLowerCase()); + assert.equal(resetApproveCall.args[1], 0n); + + const amountApproveCall = decodeFunctionData({ + abi: erc20Abi, + data: ctfTxs[1].data, + }); + assert.equal(amountApproveCall.functionName, 'approve'); + assert.equal(amountApproveCall.args[0].toLowerCase(), ctf.toLowerCase()); + assert.equal(amountApproveCall.args[1], 250000n); + + const splitCall = decodeFunctionData({ + abi: parseAbi([ + 'function splitPosition(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)', + ]), + data: ctfTxs[2].data, + }); + assert.equal(splitCall.functionName, 'splitPosition'); + assert.equal(splitCall.args[0].toLowerCase(), usdc.toLowerCase()); + assert.equal(splitCall.args[2], conditionId); + assert.deepEqual(splitCall.args[3], [1n, 2n]); + assert.equal(splitCall.args[4], 250000n); + + const ctfRedeemTxs = buildOgTransactions( + [ + { + kind: 'ctf_redeem', + ctfContract: ctf, + collateralToken: usdc, + conditionId, + indexSets: [1, 2], + operation: 1, + }, + ], + { config: {} } + ); + assert.equal(ctfRedeemTxs.length, 1); + assert.equal(ctfRedeemTxs[0].operation, 0); + + console.log('[test] buildOgTransactions uniswap + ctf_split actions OK'); +} + +run(); diff --git a/agent/scripts/test-erc1155-deposit.mjs b/agent/scripts/test-erc1155-deposit.mjs new file mode 100644 index 00000000..9a44b29d --- /dev/null +++ b/agent/scripts/test-erc1155-deposit.mjs @@ -0,0 +1,843 @@ +import assert from 'node:assert/strict'; +import { + decodeFunctionData, + encodeAbiParameters, + getCreate2Address, + keccak256, + parseAbi, +} from 'viem'; +import { makeErc1155Deposit } from '../src/lib/tx.js'; + +async function run() { + const token = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'; + const account = { address: '0x1111111111111111111111111111111111111111' }; + const config = { commitmentSafe: '0x2222222222222222222222222222222222222222' }; + + let writeContractArgs; + const walletClient = { + async writeContract(args) { + writeContractArgs = args; + return '0xabc123'; + }, + }; + + const txHash = await makeErc1155Deposit({ + walletClient, + account, + config, + token, + tokenId: '7', + amount: '3', + data: null, + }); + + assert.equal(txHash, '0xabc123'); + assert.equal(writeContractArgs.address.toLowerCase(), token.toLowerCase()); + assert.equal(writeContractArgs.functionName, 'safeTransferFrom'); + assert.equal(writeContractArgs.args[0].toLowerCase(), account.address.toLowerCase()); + assert.equal(writeContractArgs.args[1].toLowerCase(), config.commitmentSafe.toLowerCase()); + assert.equal(writeContractArgs.args[2], 7n); + assert.equal(writeContractArgs.args[3], 3n); + assert.equal(writeContractArgs.args[4], '0x'); + + await assert.rejects( + () => + makeErc1155Deposit({ + walletClient, + account, + config, + token, + tokenId: '7', + amount: '0', + data: '0x', + }), + /amount must be > 0/ + ); + + const relayerFromAddress = getCreate2Address({ + from: '0xaacfeea03eb1561c4e67d661e40682bd20e3541b', + salt: keccak256( + encodeAbiParameters( + [ + { + type: 'address', + }, + ], + [account.address] + ) + ), + bytecodeHash: + '0xb61d27f6f0f1579b6af9d23fafd567586f35f7d2f43d6bd5f85c0b690952d469', + }); + const relayedTxHash = `0x${'1'.repeat(64)}`; + const onchainTxHash = `0x${'2'.repeat(64)}`; + const relayerTransactionId = 'relayer-tx-1'; + let relayerSubmitBody; + let relayerSubmitHeaders; + let statusPollCount = 0; + let sawSubmitEndpoint = false; + const oldFetch = globalThis.fetch; + try { + globalThis.fetch = async (url, options = {}) => { + const asText = String(url); + const asLower = asText.toLowerCase(); + if (asLower.includes('/deployed?') && asLower.includes(relayerFromAddress.toLowerCase())) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ deployed: true }); + }, + }; + } + + if (asText.includes('/relay-payload?')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + address: relayerFromAddress, + nonce: '12', + }); + }, + }; + } + + if (asText.endsWith('/submit')) { + sawSubmitEndpoint = true; + relayerSubmitBody = JSON.parse(options.body); + relayerSubmitHeaders = options.headers; + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + transactionID: relayerTransactionId, + hash: relayedTxHash, + state: 'STATE_PENDING', + }); + }, + }; + } + + if ( + asText.includes('/transaction?') && + (asText.includes(`id=${relayerTransactionId}`) || + asText.includes(`transactionID=${relayerTransactionId}`)) + ) { + statusPollCount += 1; + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + if (statusPollCount === 1) { + return JSON.stringify([ + { + transactionID: relayerTransactionId, + hash: relayedTxHash, + state: 'STATE_PENDING', + }, + ]); + } + return JSON.stringify([ + { + transactionID: relayerTransactionId, + hash: relayedTxHash, + state: 'STATE_CONFIRMED', + transactionHash: onchainTxHash, + }, + ]); + }, + }; + } + + throw new Error(`Unexpected relayer fetch URL: ${asText}`); + }; + + let relayerSignedMessage; + const relayerWalletClient = { + async signMessage({ message }) { + relayerSignedMessage = message; + return `0x${'a'.repeat(128)}1b`; + }, + }; + + const relayerPublicClient = { + async getChainId() { + return 137; + }, + async readContract() { + return 12n; + }, + }; + + const relayerConfig = { + commitmentSafe: config.commitmentSafe, + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerFromAddress: relayerFromAddress, + polymarketRelayerTxType: 'SAFE', + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: Buffer.from('builder-secret').toString('base64'), + polymarketBuilderPassphrase: 'builder-passphrase', + polymarketRelayerPollIntervalMs: 0, + polymarketRelayerPollTimeoutMs: 1_000, + }; + + const relayerDepositHash = await makeErc1155Deposit({ + publicClient: relayerPublicClient, + walletClient: relayerWalletClient, + account, + config: relayerConfig, + token, + tokenId: '7', + amount: '3', + data: null, + }); + + assert.equal(relayerDepositHash, onchainTxHash); + assert.equal(sawSubmitEndpoint, true); + assert.equal(relayerSubmitBody.type, 'SAFE'); + assert.equal(relayerSubmitBody.from.toLowerCase(), account.address.toLowerCase()); + assert.equal(relayerSubmitBody.proxyWallet.toLowerCase(), relayerFromAddress.toLowerCase()); + assert.equal(relayerSubmitBody.to.toLowerCase(), token.toLowerCase()); + assert.equal(relayerSubmitBody.nonce, '12'); + assert.equal(typeof relayerSubmitBody.signatureParams, 'object'); + assert.equal(relayerSubmitBody.signatureParams.gasPrice, '0'); + assert.equal(relayerSubmitBody.signatureParams.safeTxnGas, '0'); + assert.equal(relayerSubmitBody.signatureParams.baseGas, '0'); + assert.equal(relayerSubmitBody.signatureParams.operation, '0'); + assert.equal(typeof relayerSubmitBody.signature, 'string'); + assert.equal(typeof relayerSubmitBody.metadata, 'string'); + assert.equal(relayerSubmitBody.metadata.includes('make_erc1155_deposit'), true); + assert.equal(relayerSubmitHeaders.POLY_BUILDER_API_KEY, 'builder-key'); + assert.equal(relayerSubmitHeaders.POLY_BUILDER_PASSPHRASE, 'builder-passphrase'); + assert.equal(typeof relayerSubmitHeaders.POLY_BUILDER_SIGNATURE, 'string'); + assert.equal(typeof relayerSubmitHeaders.POLY_BUILDER_TIMESTAMP, 'string'); + assert.ok(relayerSignedMessage?.raw); + + const decoded = decodeFunctionData({ + abi: parseAbi([ + 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)', + ]), + data: relayerSubmitBody.data, + }); + assert.equal(decoded.functionName, 'safeTransferFrom'); + assert.equal(decoded.args[0].toLowerCase(), relayerFromAddress.toLowerCase()); + assert.equal(decoded.args[1].toLowerCase(), config.commitmentSafe.toLowerCase()); + assert.equal(decoded.args[2], 7n); + assert.equal(decoded.args[3], 3n); + assert.equal(decoded.args[4], '0x'); + } finally { + globalThis.fetch = oldFetch; + } + + const deployedUnavailableRelayTxHash = `0x${'d'.repeat(64)}`; + const deployedUnavailableOnchainTxHash = `0x${'e'.repeat(64)}`; + const deployedUnavailableTransactionId = 'relayer-safe-deployed-unavailable-1'; + let deployedUnavailableSubmitBody; + let sawUnavailableDeployedCheck = false; + const oldFetchDeployedUnavailable = globalThis.fetch; + try { + globalThis.fetch = async (url, options = {}) => { + const asText = String(url); + const asLower = asText.toLowerCase(); + if (asLower.includes('/deployed?') && asLower.includes(relayerFromAddress.toLowerCase())) { + sawUnavailableDeployedCheck = true; + return { + ok: false, + status: 503, + statusText: 'Service Unavailable', + async text() { + return JSON.stringify({ error: 'temporarily unavailable' }); + }, + }; + } + + if (asText.includes('/relay-payload?')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + address: relayerFromAddress, + nonce: '14', + }); + }, + }; + } + + if (asText.endsWith('/submit')) { + deployedUnavailableSubmitBody = JSON.parse(options.body); + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + transactionID: deployedUnavailableTransactionId, + hash: deployedUnavailableRelayTxHash, + state: 'STATE_PENDING', + }); + }, + }; + } + + if ( + asText.includes('/transaction?') && + (asText.includes(`id=${deployedUnavailableTransactionId}`) || + asText.includes(`transactionID=${deployedUnavailableTransactionId}`)) + ) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify([ + { + transactionID: deployedUnavailableTransactionId, + hash: deployedUnavailableRelayTxHash, + state: 'STATE_CONFIRMED', + transactionHash: deployedUnavailableOnchainTxHash, + }, + ]); + }, + }; + } + + throw new Error(`Unexpected relayer fetch URL (deployed unavailable test): ${asText}`); + }; + + const deployedUnavailableDepositHash = await makeErc1155Deposit({ + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signMessage() { + return `0x${'f'.repeat(128)}1b`; + }, + }, + account, + config: { + commitmentSafe: config.commitmentSafe, + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'SAFE', + polymarketRelayerFromAddress: relayerFromAddress, + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: Buffer.from('builder-secret').toString('base64'), + polymarketBuilderPassphrase: 'builder-passphrase', + polymarketRelayerPollIntervalMs: 0, + polymarketRelayerPollTimeoutMs: 1_000, + }, + token, + tokenId: '12', + amount: '1', + data: null, + }); + + assert.equal(sawUnavailableDeployedCheck, true); + assert.equal(deployedUnavailableDepositHash, deployedUnavailableOnchainTxHash); + assert.equal( + deployedUnavailableSubmitBody.proxyWallet.toLowerCase(), + relayerFromAddress.toLowerCase() + ); + } finally { + globalThis.fetch = oldFetchDeployedUnavailable; + } + + const clobOnlyAddress = '0x3333333333333333333333333333333333333333'; + const clobIgnoredRelayTxHash = `0x${'9'.repeat(64)}`; + const clobIgnoredOnchainTxHash = `0x${'a'.repeat(64)}`; + const clobIgnoredTransactionId = 'relayer-safe-from-resolved-1'; + let clobIgnoredSubmitBody; + const oldFetchClobIgnored = globalThis.fetch; + try { + globalThis.fetch = async (url, options = {}) => { + const asText = String(url); + const asLower = asText.toLowerCase(); + if (asLower.includes('/deployed?') && asLower.includes(relayerFromAddress.toLowerCase())) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ deployed: true }); + }, + }; + } + + if (asText.includes('/relay-payload?')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + address: relayerFromAddress, + nonce: '13', + }); + }, + }; + } + + if (asText.endsWith('/submit')) { + clobIgnoredSubmitBody = JSON.parse(options.body); + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + transactionID: clobIgnoredTransactionId, + hash: clobIgnoredRelayTxHash, + state: 'STATE_PENDING', + }); + }, + }; + } + + if ( + asText.includes('/transaction?') && + (asText.includes(`id=${clobIgnoredTransactionId}`) || + asText.includes(`transactionID=${clobIgnoredTransactionId}`)) + ) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify([ + { + transactionID: clobIgnoredTransactionId, + hash: clobIgnoredRelayTxHash, + state: 'STATE_CONFIRMED', + transactionHash: clobIgnoredOnchainTxHash, + }, + ]); + }, + }; + } + + throw new Error(`Unexpected relayer fetch URL (CLOB ignored test): ${asText}`); + }; + + const clobIgnoredDepositHash = await makeErc1155Deposit({ + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signMessage() { + return `0x${'b'.repeat(128)}1b`; + }, + }, + account, + config: { + commitmentSafe: config.commitmentSafe, + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'SAFE', + polymarketClobAddress: clobOnlyAddress, + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: Buffer.from('builder-secret').toString('base64'), + polymarketBuilderPassphrase: 'builder-passphrase', + polymarketRelayerPollIntervalMs: 0, + polymarketRelayerPollTimeoutMs: 1_000, + }, + token, + tokenId: '11', + amount: '2', + data: null, + }); + + assert.equal(clobIgnoredDepositHash, clobIgnoredOnchainTxHash); + assert.equal(clobIgnoredSubmitBody.proxyWallet.toLowerCase(), relayerFromAddress.toLowerCase()); + assert.notEqual(clobIgnoredSubmitBody.proxyWallet.toLowerCase(), clobOnlyAddress.toLowerCase()); + + const decodedClobIgnored = decodeFunctionData({ + abi: parseAbi([ + 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)', + ]), + data: clobIgnoredSubmitBody.data, + }); + assert.equal(decodedClobIgnored.functionName, 'safeTransferFrom'); + assert.equal(decodedClobIgnored.args[0].toLowerCase(), relayerFromAddress.toLowerCase()); + assert.notEqual(decodedClobIgnored.args[0].toLowerCase(), clobOnlyAddress.toLowerCase()); + } finally { + globalThis.fetch = oldFetchClobIgnored; + } + + const proxyWalletAddress = '0x5555555555555555555555555555555555555555'; + const proxyRelayTxHash = `0x${'3'.repeat(64)}`; + const proxyOnchainTxHash = `0x${'4'.repeat(64)}`; + const proxyTransactionId = 'relayer-proxy-tx-1'; + let proxySubmitBody; + const oldFetchProxy = globalThis.fetch; + try { + globalThis.fetch = async (url, options = {}) => { + const asText = String(url); + if (asText.includes('/relay-payload?')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + address: proxyWalletAddress, + nonce: '3', + }); + }, + }; + } + + if (asText.endsWith('/submit')) { + proxySubmitBody = JSON.parse(options.body); + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + transactionID: proxyTransactionId, + hash: proxyRelayTxHash, + state: 'STATE_PENDING', + }); + }, + }; + } + + if (asText.includes('/transaction?') && asText.includes(proxyTransactionId)) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify([ + { + transactionID: proxyTransactionId, + hash: proxyRelayTxHash, + state: 'STATE_CONFIRMED', + transactionHash: proxyOnchainTxHash, + }, + ]); + }, + }; + } + + throw new Error(`Unexpected PROXY relayer fetch URL: ${asText}`); + }; + + let proxySignedMessage; + const proxyWalletClient = { + async signMessage({ message }) { + proxySignedMessage = message; + return `0x${'c'.repeat(128)}1b`; + }, + }; + const proxyPublicClient = { + async getChainId() { + return 137; + }, + }; + + const proxyConfig = { + commitmentSafe: config.commitmentSafe, + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'PROXY', + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: Buffer.from('builder-secret').toString('base64'), + polymarketBuilderPassphrase: 'builder-passphrase', + polymarketRelayerPollIntervalMs: 0, + polymarketRelayerPollTimeoutMs: 1_000, + }; + + const proxyDepositHash = await makeErc1155Deposit({ + publicClient: proxyPublicClient, + walletClient: proxyWalletClient, + account, + config: proxyConfig, + token, + tokenId: '8', + amount: '4', + data: null, + }); + + assert.equal(proxyDepositHash, proxyOnchainTxHash); + assert.equal(proxySubmitBody.type, 'PROXY'); + assert.equal(proxySubmitBody.from.toLowerCase(), account.address.toLowerCase()); + assert.equal(proxySubmitBody.proxyWallet.toLowerCase(), proxyWalletAddress.toLowerCase()); + assert.equal(proxySubmitBody.signatureParams.chainId, '137'); + assert.equal(proxySubmitBody.nonce, '3'); + assert.ok(proxySignedMessage?.raw); + + const decodedProxy = decodeFunctionData({ + abi: parseAbi([ + 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)', + ]), + data: proxySubmitBody.data, + }); + assert.equal(decodedProxy.args[0].toLowerCase(), proxyWalletAddress.toLowerCase()); + assert.equal(decodedProxy.args[1].toLowerCase(), config.commitmentSafe.toLowerCase()); + assert.equal(decodedProxy.args[2], 8n); + assert.equal(decodedProxy.args[3], 4n); + } finally { + globalThis.fetch = oldFetchProxy; + } + + const safeCreateRelayHash = `0x${'5'.repeat(64)}`; + const safeCreateOnchainHash = `0x${'6'.repeat(64)}`; + const safeActionRelayHash = `0x${'7'.repeat(64)}`; + const safeActionOnchainHash = `0x${'8'.repeat(64)}`; + const safeCreateTransactionId = 'safe-create-1'; + const safeActionTransactionId = 'safe-action-1'; + let safeCreateSubmitBody; + let safeActionSubmitBody; + let submitCount = 0; + let signTypedDataCalled = false; + const oldFetchSafeCreate = globalThis.fetch; + try { + globalThis.fetch = async (url, options = {}) => { + const asText = String(url); + const asLower = asText.toLowerCase(); + + if (asText.includes('/relay-payload?')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + address: relayerFromAddress, + nonce: '9', + }); + }, + }; + } + + if (asLower.includes('/deployed?') && asLower.includes(relayerFromAddress.toLowerCase())) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ deployed: false }); + }, + }; + } + + if (asText.endsWith('/submit')) { + submitCount += 1; + const body = JSON.parse(options.body); + if (submitCount === 1) { + safeCreateSubmitBody = body; + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + transactionID: safeCreateTransactionId, + hash: safeCreateRelayHash, + state: 'STATE_PENDING', + }); + }, + }; + } + safeActionSubmitBody = body; + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + transactionID: safeActionTransactionId, + hash: safeActionRelayHash, + state: 'STATE_PENDING', + }); + }, + }; + } + + if (asText.includes('/transaction?') && asText.includes(safeCreateTransactionId)) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify([ + { + transactionID: safeCreateTransactionId, + hash: safeCreateRelayHash, + state: 'STATE_CONFIRMED', + transactionHash: safeCreateOnchainHash, + }, + ]); + }, + }; + } + + if (asText.includes('/transaction?') && asText.includes(safeActionTransactionId)) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify([ + { + transactionID: safeActionTransactionId, + hash: safeActionRelayHash, + state: 'STATE_CONFIRMED', + transactionHash: safeActionOnchainHash, + }, + ]); + }, + }; + } + + throw new Error(`Unexpected SAFE-CREATE relayer fetch URL: ${asText}`); + }; + + const safeCreateWalletClient = { + async signTypedData(args) { + signTypedDataCalled = true; + assert.equal(args.primaryType, 'CreateProxy'); + return `0x${'d'.repeat(130)}`; + }, + async signMessage() { + return `0x${'e'.repeat(128)}1b`; + }, + }; + const safeCreatePublicClient = { + async getChainId() { + return 137; + }, + }; + + const safeCreateConfig = { + commitmentSafe: config.commitmentSafe, + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'SAFE', + polymarketRelayerAutoDeployProxy: true, + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: Buffer.from('builder-secret').toString('base64'), + polymarketBuilderPassphrase: 'builder-passphrase', + polymarketRelayerPollIntervalMs: 0, + polymarketRelayerPollTimeoutMs: 1_000, + }; + + const safeCreateDepositHash = await makeErc1155Deposit({ + publicClient: safeCreatePublicClient, + walletClient: safeCreateWalletClient, + account, + config: safeCreateConfig, + token, + tokenId: '9', + amount: '5', + data: null, + }); + + assert.equal(safeCreateDepositHash, safeActionOnchainHash); + assert.equal(signTypedDataCalled, true); + assert.equal(safeCreateSubmitBody.type, 'SAFE-CREATE'); + assert.equal(safeCreateSubmitBody.from.toLowerCase(), account.address.toLowerCase()); + assert.equal(safeCreateSubmitBody.proxyWallet.toLowerCase(), relayerFromAddress.toLowerCase()); + assert.equal(safeActionSubmitBody.type, 'SAFE'); + assert.equal(safeActionSubmitBody.proxyWallet.toLowerCase(), relayerFromAddress.toLowerCase()); + } finally { + globalThis.fetch = oldFetchSafeCreate; + } + + const oldFetchMissingTracking = globalThis.fetch; + try { + globalThis.fetch = async (url) => { + const asText = String(url); + const asLower = asText.toLowerCase(); + if (asText.includes('/relay-payload?')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ + address: relayerFromAddress, + nonce: '12', + }); + }, + }; + } + if (asLower.includes('/deployed?') && asLower.includes(relayerFromAddress.toLowerCase())) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({ deployed: true }); + }, + }; + } + if (asText.endsWith('/submit')) { + return { + ok: true, + status: 200, + statusText: 'OK', + async text() { + return JSON.stringify({}); + }, + }; + } + throw new Error(`Unexpected missing-tracking fetch URL: ${asText}`); + }; + + await assert.rejects( + () => + makeErc1155Deposit({ + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signMessage() { + return `0x${'f'.repeat(128)}1b`; + }, + }, + account, + config: { + commitmentSafe: config.commitmentSafe, + polymarketRelayerEnabled: true, + polymarketRelayerHost: 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: 'SAFE', + polymarketRelayerFromAddress: relayerFromAddress, + polymarketBuilderApiKey: 'builder-key', + polymarketBuilderSecret: Buffer.from('builder-secret').toString('base64'), + polymarketBuilderPassphrase: 'builder-passphrase', + polymarketRelayerPollIntervalMs: 0, + polymarketRelayerPollTimeoutMs: 1_000, + }, + token, + tokenId: '10', + amount: '1', + data: null, + }), + /did not return transactionID or txHash/ + ); + } finally { + globalThis.fetch = oldFetchMissingTracking; + } + + console.log('[test] makeErc1155Deposit OK'); +} + +run(); diff --git a/agent/scripts/test-polling-balance-transitions.mjs b/agent/scripts/test-polling-balance-transitions.mjs new file mode 100644 index 00000000..48bba257 --- /dev/null +++ b/agent/scripts/test-polling-balance-transitions.mjs @@ -0,0 +1,84 @@ +import assert from 'node:assert/strict'; +import { pollCommitmentChanges } from '../src/lib/polling.js'; + +const TOKEN = '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238'; +const TOKEN_ZERO = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; +const TOKEN_NEW_ZERO = '0x7b79995e5f793a07bc00c21412e50ecae098e7f9'; +const SAFE = '0x1234000000000000000000000000000000000000'; + +function createClient({ getTokenBalance }) { + let block = 100n; + return { + getBlockNumber: async () => { + block += 1n; + return block; + }, + readContract: async ({ address }) => getTokenBalance({ block, address }), + getLogs: async () => [], + getBalance: async () => 0n, + }; +} + +async function run() { + const publicClient = createClient({ + getTokenBalance: ({ block, address }) => { + const token = String(address).toLowerCase(); + if (token === TOKEN_ZERO) return 0n; + if (token === TOKEN_NEW_ZERO) return 0n; + if (block === 101n) return 30000n; + if (block === 102n) return 30000n; + if (block === 103n) return 25000n; + return 0n; + }, + }); + const trackedAssets = new Set([TOKEN, TOKEN_ZERO]); + + const first = await pollCommitmentChanges({ + publicClient, + trackedAssets, + commitmentSafe: SAFE, + watchNativeBalance: false, + lastCheckedBlock: undefined, + lastNativeBalance: undefined, + lastAssetBalances: undefined, + }); + const firstSnapshots = first.balanceSnapshots.filter((s) => s.kind === 'erc20BalanceSnapshot'); + assert.equal(first.deposits.length, 0); + assert.equal(firstSnapshots.length, 1); + assert.equal(BigInt(firstSnapshots[0].amount), 30000n); + + trackedAssets.add(TOKEN_NEW_ZERO); + const second = await pollCommitmentChanges({ + publicClient, + trackedAssets, + commitmentSafe: SAFE, + watchNativeBalance: false, + lastCheckedBlock: first.lastCheckedBlock, + lastNativeBalance: first.lastNativeBalance, + lastAssetBalances: first.lastAssetBalances, + }); + const secondSnapshots = second.balanceSnapshots.filter((s) => s.kind === 'erc20BalanceSnapshot'); + assert.equal(second.deposits.length, 0); + assert.equal(secondSnapshots.length, 0); + + const third = await pollCommitmentChanges({ + publicClient, + trackedAssets, + commitmentSafe: SAFE, + watchNativeBalance: false, + lastCheckedBlock: second.lastCheckedBlock, + lastNativeBalance: second.lastNativeBalance, + lastAssetBalances: second.lastAssetBalances, + }); + const thirdSnapshots = third.balanceSnapshots.filter((s) => s.kind === 'erc20BalanceSnapshot'); + assert.equal(third.deposits.length, 0); + assert.equal(thirdSnapshots.length, 1); + assert.equal(BigInt(thirdSnapshots[0].amount), 25000n); + + console.log('[test] polling balance transition gating OK'); +} + +run().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/agent/scripts/test-polymarket-request-retries.mjs b/agent/scripts/test-polymarket-request-retries.mjs new file mode 100644 index 00000000..ed816c19 --- /dev/null +++ b/agent/scripts/test-polymarket-request-retries.mjs @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict'; +import { cancelClobOrders, placeClobOrder } from '../src/lib/polymarket.js'; + +const TEST_CONFIG = { + polymarketClobHost: 'https://clob.polymarket.com', + polymarketClobApiKey: 'test-api-key', + polymarketClobApiSecret: Buffer.from('test-secret').toString('base64'), + polymarketClobApiPassphrase: 'test-passphrase', + polymarketClobRequestTimeoutMs: 1_000, + polymarketClobMaxRetries: 2, + polymarketClobRetryDelayMs: 0, +}; + +function jsonResponse(status, body, statusText = '') { + const text = JSON.stringify(body); + return { + ok: status >= 200 && status < 300, + status, + statusText, + async text() { + return text; + }, + }; +} + +async function run() { + const originalFetch = globalThis.fetch; + try { + let orderCalls = 0; + globalThis.fetch = async () => { + orderCalls += 1; + return jsonResponse(500, { error: 'temporary failure' }, 'Internal Server Error'); + }; + + await assert.rejects( + placeClobOrder({ + config: TEST_CONFIG, + signingAddress: '0x1111111111111111111111111111111111111111', + signedOrder: { + maker: '0x1111111111111111111111111111111111111111', + tokenId: '1', + side: 'BUY', + }, + ownerApiKey: 'owner-key', + orderType: 'GTC', + }), + /CLOB request failed \(POST \/order\): 500/ + ); + assert.equal(orderCalls, 1); + + let cancelCalls = 0; + globalThis.fetch = async () => { + cancelCalls += 1; + if (cancelCalls === 1) { + return jsonResponse(500, { error: 'temporary failure' }, 'Internal Server Error'); + } + return jsonResponse(200, { canceled: ['order-1'] }); + }; + + const cancelResult = await cancelClobOrders({ + config: TEST_CONFIG, + signingAddress: '0x1111111111111111111111111111111111111111', + mode: 'ids', + orderIds: ['order-1'], + }); + assert.deepEqual(cancelResult, { canceled: ['order-1'] }); + assert.equal(cancelCalls, 2); + + console.log('[test] polymarket request retries OK'); + } finally { + globalThis.fetch = originalFetch; + } +} + +run(); diff --git a/agent/scripts/test-polymarket-tool-normalization.mjs b/agent/scripts/test-polymarket-tool-normalization.mjs new file mode 100644 index 00000000..071e0984 --- /dev/null +++ b/agent/scripts/test-polymarket-tool-normalization.mjs @@ -0,0 +1,534 @@ +import assert from 'node:assert/strict'; +import { executeToolCalls, toolDefinitions } from '../src/lib/tools.js'; + +const TEST_ACCOUNT = { address: '0x1111111111111111111111111111111111111111' }; +const TEST_SIGNATURE = `0x${'1'.repeat(130)}`; + +function parseToolOutput(output) { + return JSON.parse(output.output); +} + +async function run() { + const defs = toolDefinitions({ + proposeEnabled: false, + disputeEnabled: false, + clobEnabled: true, + }); + const placeOrderDef = defs.find((tool) => tool.name === 'polymarket_clob_place_order'); + const buildSignAndPlaceOrderDef = defs.find( + (tool) => tool.name === 'polymarket_clob_build_sign_and_place_order' + ); + const cancelOrdersDef = defs.find((tool) => tool.name === 'polymarket_clob_cancel_orders'); + const makeDepositDef = defs.find((tool) => tool.name === 'make_deposit'); + const makeErc1155DepositDef = defs.find((tool) => tool.name === 'make_erc1155_deposit'); + + assert.ok(placeOrderDef); + assert.ok(buildSignAndPlaceOrderDef); + assert.ok(cancelOrdersDef); + assert.equal(makeDepositDef, undefined); + assert.equal(makeErc1155DepositDef, undefined); + assert.deepEqual(placeOrderDef.parameters.properties.orderType.enum, ['GTC', 'GTD', 'FOK', 'FAK']); + assert.deepEqual(buildSignAndPlaceOrderDef.parameters.properties.orderType.enum, [ + 'GTC', + 'GTD', + 'FOK', + 'FAK', + ]); + assert.deepEqual(cancelOrdersDef.parameters.properties.mode.enum, ['ids', 'market', 'all']); + assert.deepEqual(cancelOrdersDef.parameters.required, ['mode']); + + const config = { + polymarketClobEnabled: true, + polymarketClobHost: 'https://clob.polymarket.com', + polymarketClobApiKey: 'dummy-api-key', + // Keep secret/passphrase absent so calls fail before any network request. + polymarketClobApiSecret: undefined, + polymarketClobApiPassphrase: undefined, + }; + + const invalidOrderType = await executeToolCalls({ + toolCalls: [ + { + callId: 'invalid-order-type', + name: 'polymarket_clob_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'LIMIT', + signedOrder: { side: 'BUY', tokenId: '123' }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const invalidOrderTypeOut = parseToolOutput(invalidOrderType[0]); + assert.equal(invalidOrderTypeOut.status, 'error'); + assert.match(invalidOrderTypeOut.message, /orderType must be one of/); + + const normalizedOrderType = await executeToolCalls({ + toolCalls: [ + { + callId: 'normalized-order-type', + name: 'polymarket_clob_place_order', + arguments: { + side: ' buy ', + tokenId: '123', + orderType: ' gtc ', + signedOrder: { + side: 'BUY', + tokenId: '123', + maker: TEST_ACCOUNT.address, + }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const normalizedOrderTypeOut = parseToolOutput(normalizedOrderType[0]); + assert.equal(normalizedOrderTypeOut.status, 'error'); + assert.match(normalizedOrderTypeOut.message, /Missing CLOB credentials/); + + const missingIdentity = await executeToolCalls({ + toolCalls: [ + { + callId: 'missing-identity', + name: 'polymarket_clob_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + signedOrder: { + side: 'BUY', + tokenId: '123', + }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const missingIdentityOut = parseToolOutput(missingIdentity[0]); + assert.equal(missingIdentityOut.status, 'error'); + assert.match(missingIdentityOut.message, /must include an identity field/); + + const mismatchedIdentity = await executeToolCalls({ + toolCalls: [ + { + callId: 'mismatched-identity', + name: 'polymarket_clob_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + signedOrder: { + side: 'BUY', + tokenId: '123', + maker: '0x3333333333333333333333333333333333333333', + }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const mismatchedIdentityOut = parseToolOutput(mismatchedIdentity[0]); + assert.equal(mismatchedIdentityOut.status, 'error'); + assert.match(mismatchedIdentityOut.message, /signedOrder identity mismatch/); + + const mixedIdentityWrappedOrder = await executeToolCalls({ + toolCalls: [ + { + callId: 'mixed-identity-wrapped-order', + name: 'polymarket_clob_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + signedOrder: { + // Wrapper identity should not override nested submitted order identity. + maker: TEST_ACCOUNT.address, + order: { + side: 'BUY', + tokenId: '123', + maker: '0x3333333333333333333333333333333333333333', + }, + }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const mixedIdentityWrappedOrderOut = parseToolOutput(mixedIdentityWrappedOrder[0]); + assert.equal(mixedIdentityWrappedOrderOut.status, 'error'); + assert.match(mixedIdentityWrappedOrderOut.message, /signedOrder identity mismatch/); + + const missingNestedSideInWrappedOrder = await executeToolCalls({ + toolCalls: [ + { + callId: 'missing-nested-side', + name: 'polymarket_clob_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + signedOrder: { + // Nested payload is what will be submitted and must carry side/token. + side: 'BUY', + maker: TEST_ACCOUNT.address, + order: { + tokenId: '123', + maker: TEST_ACCOUNT.address, + }, + }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const missingNestedSideInWrappedOrderOut = parseToolOutput(missingNestedSideInWrappedOrder[0]); + assert.equal(missingNestedSideInWrappedOrderOut.status, 'error'); + assert.match(missingNestedSideInWrappedOrderOut.message, /must include embedded side and token id/); + + const configuredIdentityMatch = await executeToolCalls({ + toolCalls: [ + { + callId: 'configured-identity-match', + name: 'polymarket_clob_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + signedOrder: { + side: 'BUY', + tokenId: '123', + maker: '0x3333333333333333333333333333333333333333', + }, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config: { + ...config, + polymarketClobAddress: '0x3333333333333333333333333333333333333333', + }, + ogContext: null, + }); + const configuredIdentityMatchOut = parseToolOutput(configuredIdentityMatch[0]); + assert.equal(configuredIdentityMatchOut.status, 'error'); + assert.match(configuredIdentityMatchOut.message, /Missing CLOB credentials/); + + const missingTypedDataSupport = await executeToolCalls({ + toolCalls: [ + { + callId: 'missing-typed-data-support', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + makerAmount: '1000000', + takerAmount: '400000', + }, + }, + ], + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const missingTypedDataSupportOut = parseToolOutput(missingTypedDataSupport[0]); + assert.equal(missingTypedDataSupportOut.status, 'error'); + assert.match(missingTypedDataSupportOut.message, /signTypedData/); + + const recordedSignInputs = []; + const buildSignAndPlace = await executeToolCalls({ + toolCalls: [ + { + callId: 'build-sign-place', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: { + side: ' buy ', + tokenId: '123', + orderType: ' gtc ', + makerAmount: '1000000', + takerAmount: '450000', + signatureType: 'EOA', + }, + }, + ], + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signTypedData(args) { + recordedSignInputs.push(args); + return TEST_SIGNATURE; + }, + }, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const buildSignAndPlaceOut = parseToolOutput(buildSignAndPlace[0]); + assert.equal(buildSignAndPlaceOut.status, 'error'); + assert.match(buildSignAndPlaceOut.message, /Missing CLOB credentials/); + assert.equal(recordedSignInputs.length, 1); + assert.equal(recordedSignInputs[0].domain.chainId, 137); + assert.equal( + recordedSignInputs[0].domain.verifyingContract.toLowerCase(), + '0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e' + ); + assert.equal(recordedSignInputs[0].message.side, 0); + assert.equal(recordedSignInputs[0].message.signatureType, 0); + assert.equal(recordedSignInputs[0].message.tokenId, 123n); + + const proxySigWithoutClobAddress = await executeToolCalls({ + toolCalls: [ + { + callId: 'proxy-sig-without-clob-address', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + makerAmount: '1000000', + takerAmount: '450000', + }, + }, + ], + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signTypedData() { + return TEST_SIGNATURE; + }, + }, + account: TEST_ACCOUNT, + config: { + ...config, + polymarketClobSignatureType: 'POLY_GNOSIS_SAFE', + }, + ogContext: null, + }); + const proxySigWithoutClobAddressOut = parseToolOutput(proxySigWithoutClobAddress[0]); + assert.equal(proxySigWithoutClobAddressOut.status, 'error'); + assert.match(proxySigWithoutClobAddressOut.message, /POLYMARKET_CLOB_ADDRESS is required/); + + const recordedSafeSignInputs = []; + const defaultSafeSignatureType = await executeToolCalls({ + toolCalls: [ + { + callId: 'default-safe-signature-type', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + makerAmount: '1000000', + takerAmount: '450000', + }, + }, + ], + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signTypedData(args) { + recordedSafeSignInputs.push(args); + return TEST_SIGNATURE; + }, + }, + account: TEST_ACCOUNT, + config: { + ...config, + polymarketClobAddress: '0x3333333333333333333333333333333333333333', + polymarketClobSignatureType: 'POLY_GNOSIS_SAFE', + }, + ogContext: null, + }); + const defaultSafeSignatureTypeOut = parseToolOutput(defaultSafeSignatureType[0]); + assert.equal(defaultSafeSignatureTypeOut.status, 'error'); + assert.match(defaultSafeSignatureTypeOut.message, /Missing CLOB credentials/); + assert.equal(recordedSafeSignInputs.length, 1); + assert.equal(recordedSafeSignInputs[0].message.signatureType, 2); + + const invalidBuildSignIdentity = await executeToolCalls({ + toolCalls: [ + { + callId: 'invalid-build-sign-identity', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + makerAmount: '1000000', + takerAmount: '450000', + maker: '0x3333333333333333333333333333333333333333', + }, + }, + ], + publicClient: { + async getChainId() { + return 137; + }, + }, + walletClient: { + async signTypedData() { + return TEST_SIGNATURE; + }, + }, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const invalidBuildSignIdentityOut = parseToolOutput(invalidBuildSignIdentity[0]); + assert.equal(invalidBuildSignIdentityOut.status, 'error'); + assert.match(invalidBuildSignIdentityOut.message, /maker identity mismatch/); + + const missingChainIdForBuildSign = await executeToolCalls({ + toolCalls: [ + { + callId: 'missing-chain-id-build-sign', + name: 'polymarket_clob_build_sign_and_place_order', + arguments: { + side: 'BUY', + tokenId: '123', + orderType: 'GTC', + makerAmount: '1000000', + takerAmount: '450000', + }, + }, + ], + publicClient: {}, + walletClient: { + async signTypedData() { + return TEST_SIGNATURE; + }, + }, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const missingChainIdForBuildSignOut = parseToolOutput(missingChainIdForBuildSign[0]); + assert.equal(missingChainIdForBuildSignOut.status, 'error'); + assert.match(missingChainIdForBuildSignOut.message, /chainId is required/); + + const invalidCancelMode = await executeToolCalls({ + toolCalls: [ + { + callId: 'invalid-cancel-mode', + name: 'polymarket_clob_cancel_orders', + arguments: { + mode: 'nope', + orderIds: ['order-1'], + market: null, + assetId: null, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const invalidCancelModeOut = parseToolOutput(invalidCancelMode[0]); + assert.equal(invalidCancelModeOut.status, 'error'); + assert.match(invalidCancelModeOut.message, /mode must be one of ids, market, all/); + + const normalizedCancelMode = await executeToolCalls({ + toolCalls: [ + { + callId: 'normalized-cancel-mode', + name: 'polymarket_clob_cancel_orders', + arguments: { + mode: ' IDS ', + orderIds: ['order-1'], + market: null, + assetId: null, + }, + }, + ], + publicClient: {}, + walletClient: {}, + account: TEST_ACCOUNT, + config, + ogContext: null, + }); + const normalizedCancelModeOut = parseToolOutput(normalizedCancelMode[0]); + assert.equal(normalizedCancelModeOut.status, 'error'); + assert.match(normalizedCancelModeOut.message, /Missing CLOB credentials/); + + const blockedOnchainDeposit = await executeToolCalls({ + toolCalls: [ + { + callId: 'blocked-onchain-deposit', + name: 'make_deposit', + arguments: { + asset: '0x0000000000000000000000000000000000000000', + amountWei: '1', + }, + }, + ], + publicClient: { + async waitForTransactionReceipt() { + throw new Error('should not be called'); + }, + }, + walletClient: { + async sendTransaction() { + throw new Error('should not be called'); + }, + }, + account: TEST_ACCOUNT, + config: { + ...config, + proposeEnabled: false, + disputeEnabled: false, + }, + ogContext: null, + }); + const blockedOnchainDepositOut = parseToolOutput(blockedOnchainDeposit[0]); + assert.equal(blockedOnchainDepositOut.status, 'skipped'); + assert.equal(blockedOnchainDepositOut.reason, 'onchain tools disabled'); + + console.log('[test] polymarket tool normalization OK'); +} + +run(); diff --git a/agent/scripts/test-tracked-assets-normalization.mjs b/agent/scripts/test-tracked-assets-normalization.mjs new file mode 100644 index 00000000..3a73f307 --- /dev/null +++ b/agent/scripts/test-tracked-assets-normalization.mjs @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import { loadOptimisticGovernorDefaults } from '../src/lib/og.js'; + +const COLLATERAL_CHECKSUM = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'; +const COLLATERAL_LOWER = COLLATERAL_CHECKSUM.toLowerCase(); +const OG_MODULE = '0x0000000000000000000000000000000000000001'; + +async function run() { + const trackedAssets = new Set([COLLATERAL_LOWER]); + const publicClient = { + readContract: async () => COLLATERAL_CHECKSUM, + }; + + await loadOptimisticGovernorDefaults({ + publicClient, + ogModule: OG_MODULE, + trackedAssets, + }); + + assert.equal(trackedAssets.size, 1); + assert.equal(trackedAssets.has(COLLATERAL_LOWER), true); + + console.log('[test] tracked asset normalization OK'); +} + +run().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/agent/scripts/test-uniswap-v3-price-signals.mjs b/agent/scripts/test-uniswap-v3-price-signals.mjs new file mode 100644 index 00000000..ca315ae5 --- /dev/null +++ b/agent/scripts/test-uniswap-v3-price-signals.mjs @@ -0,0 +1,131 @@ +import assert from 'node:assert/strict'; +import { collectPriceTriggerSignals } from '../src/lib/uniswapV3Price.js'; + +const WETH = '0xC02aaA39b223FE8D0A0E5C4F27eAD9083C756Cc2'; +const USDC = '0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; +const UMA = '0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828'; +const POOL_ETH_USDC = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8'; +const POOL_UMA_USDC = '0x88D97d199b9ED37C29D846d00D443De980832a22'; + +function sqrtPriceX96ForQuotePerBase({ quotePerBase, baseDecimals, quoteDecimals }) { + const raw = quotePerBase * 10 ** (quoteDecimals - baseDecimals); + const sqrtPrice = Math.sqrt(raw); + return BigInt(Math.floor(sqrtPrice * 2 ** 96)); +} + +function buildMockPublicClient() { + const slot0ByPool = new Map([ + [ + POOL_ETH_USDC.toLowerCase(), + [sqrtPriceX96ForQuotePerBase({ quotePerBase: 3200, baseDecimals: 18, quoteDecimals: 6 })], + ], + [ + POOL_UMA_USDC.toLowerCase(), + [sqrtPriceX96ForQuotePerBase({ quotePerBase: 2.0, baseDecimals: 18, quoteDecimals: 6 })], + ], + ]); + + return { + async readContract({ address, functionName }) { + const addr = address.toLowerCase(); + if (functionName === 'token0') { + if (addr === POOL_ETH_USDC.toLowerCase()) return WETH; + if (addr === POOL_UMA_USDC.toLowerCase()) return UMA; + } + if (functionName === 'token1') { + if (addr === POOL_ETH_USDC.toLowerCase()) return USDC; + if (addr === POOL_UMA_USDC.toLowerCase()) return USDC; + } + if (functionName === 'slot0') { + return slot0ByPool.get(addr); + } + if (functionName === 'fee') { + return 3000; + } + if (functionName === 'decimals') { + if (addr === WETH.toLowerCase() || addr === UMA.toLowerCase()) return 18; + if (addr === USDC.toLowerCase()) return 6; + } + throw new Error(`Unexpected mock readContract call: ${functionName} ${address}`); + }, + }; +} + +async function run() { + const publicClient = buildMockPublicClient(); + const config = { + uniswapV3FeeTiers: [500, 3000, 10000], + }; + const triggerState = new Map(); + const tokenMetaCache = new Map(); + const poolMetaCache = new Map(); + const resolvedPoolCache = new Map(); + + const signals = await collectPriceTriggerSignals({ + publicClient, + config, + triggers: [ + { + id: 'eth-breakout', + label: 'ETH >= 3200', + pool: POOL_ETH_USDC, + baseToken: WETH, + quoteToken: USDC, + comparator: 'gte', + threshold: 3200, + priority: 0, + emitOnce: true, + }, + { + id: 'uma-drop', + label: 'UMA <= 2.1', + pool: POOL_UMA_USDC, + baseToken: UMA, + quoteToken: USDC, + comparator: 'lte', + threshold: 2.1, + priority: 1, + emitOnce: true, + }, + ], + nowMs: Date.now(), + triggerState, + tokenMetaCache, + poolMetaCache, + resolvedPoolCache, + }); + + assert.equal(signals.length, 2); + assert.equal(signals[0].triggerId, 'eth-breakout'); + assert.equal(signals[1].triggerId, 'uma-drop'); + + const secondPass = await collectPriceTriggerSignals({ + publicClient, + config, + triggers: [ + { + id: 'eth-breakout', + pool: POOL_ETH_USDC, + baseToken: WETH, + quoteToken: USDC, + comparator: 'gte', + threshold: 3200, + priority: 0, + emitOnce: true, + }, + ], + nowMs: Date.now(), + triggerState, + tokenMetaCache, + poolMetaCache, + resolvedPoolCache, + }); + + assert.equal(secondPass.length, 0); + console.log('[test] price trigger signal collection OK'); +} + +run().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/agent/scripts/update-agent-metadata.mjs b/agent/scripts/update-agent-metadata.mjs new file mode 100644 index 00000000..2981fcc5 --- /dev/null +++ b/agent/scripts/update-agent-metadata.mjs @@ -0,0 +1,103 @@ +import dotenv from 'dotenv'; +import { readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '../..'); + +dotenv.config(); +dotenv.config({ path: path.resolve(repoRoot, 'agent/.env') }); + +function getArgValue(prefix) { + const arg = process.argv.find((value) => value.startsWith(prefix)); + return arg ? arg.slice(prefix.length) : null; +} + +function formatCaip10(chainId, address) { + const cleaned = address.toLowerCase(); + return `eip155:${chainId}:${cleaned}`; +} + +async function main() { + const agentRef = getArgValue('--agent=') ?? process.env.AGENT_MODULE ?? 'default'; + const agentName = agentRef.includes('/') + ? path.basename(agentRef.endsWith('.js') ? path.dirname(agentRef) : agentRef) + : agentRef; + const agentDir = agentRef.includes('/') + ? agentRef.endsWith('.js') + ? path.dirname(agentRef) + : agentRef + : `agent-library/agents/${agentName}`; + const agentJsonPath = path.resolve(repoRoot, agentDir, 'agent.json'); + + const agentIdArg = getArgValue('--agent-id='); + const agentId = agentIdArg ?? process.env.AGENT_ID; + if (!agentId) { + throw new Error('Missing --agent-id or AGENT_ID.'); + } + + const chainId = getArgValue('--chain-id=') ?? process.env.CHAIN_ID ?? '11155111'; + const wallet = getArgValue('--agent-wallet=') ?? process.env.AGENT_WALLET; + if (!wallet) { + throw new Error('Missing --agent-wallet or AGENT_WALLET.'); + } + + const registry = + getArgValue('--agent-registry=') ?? + process.env.AGENT_REGISTRY ?? + '0x8004A818BFB912233c491871b3d84c89A494BD9e'; + + const raw = await readFile(agentJsonPath, 'utf8'); + const json = JSON.parse(raw); + + json.endpoints = Array.isArray(json.endpoints) ? json.endpoints : []; + const walletEndpoint = formatCaip10(chainId, wallet); + const existingEndpoint = json.endpoints.find((item) => item?.name === 'agentWallet'); + if (existingEndpoint) { + existingEndpoint.endpoint = walletEndpoint; + } else { + json.endpoints.push({ name: 'agentWallet', endpoint: walletEndpoint }); + } + + json.registrations = Array.isArray(json.registrations) ? json.registrations : []; + const registryEndpoint = formatCaip10(chainId, registry).toLowerCase(); + const agentIdValue = String(agentId); + const normalizedRegistrations = []; + let updated = false; + for (const entry of json.registrations) { + if (!entry?.agentRegistry) continue; + const normalizedRegistry = String(entry.agentRegistry).toLowerCase(); + if (normalizedRegistry === registryEndpoint) { + if (!updated) { + normalizedRegistrations.push({ + agentId: agentIdValue, + agentRegistry: registryEndpoint, + }); + updated = true; + } + continue; + } + normalizedRegistrations.push({ + ...entry, + agentRegistry: normalizedRegistry, + }); + } + if (!updated) { + normalizedRegistrations.push({ + agentId: agentIdValue, + agentRegistry: registryEndpoint, + }); + } + json.registrations = normalizedRegistrations; + + await writeFile(agentJsonPath, `${JSON.stringify(json, null, 2)}\n`, 'utf8'); + + console.log('[agent] Updated metadata:', agentJsonPath); +} + +main().catch((error) => { + console.error('[agent] update failed:', error.message ?? error); + process.exit(1); +}); diff --git a/agent/scripts/validate-agent.mjs b/agent/scripts/validate-agent.mjs new file mode 100644 index 00000000..2cedfe10 --- /dev/null +++ b/agent/scripts/validate-agent.mjs @@ -0,0 +1,50 @@ +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +function getArgValue(prefix) { + const arg = process.argv.find((value) => value.startsWith(prefix)); + return arg ? arg.slice(prefix.length) : null; +} + +async function main() { + const moduleArg = getArgValue('--module='); + const agentRef = moduleArg ?? process.env.AGENT_MODULE ?? 'default'; + const modulePath = agentRef.includes('/') + ? agentRef + : `agent-library/agents/${agentRef}/agent.js`; + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const repoRoot = path.resolve(__dirname, '../..'); + const resolvedPath = path.isAbsolute(modulePath) + ? modulePath + : path.resolve(repoRoot, modulePath); + + const agentModule = await import(pathToFileURL(resolvedPath).href); + if (typeof agentModule.getSystemPrompt !== 'function') { + throw new Error('Agent module must export getSystemPrompt().'); + } + + const commitmentPath = path.join(path.dirname(resolvedPath), 'commitment.txt'); + const commitmentText = (await readFile(commitmentPath, 'utf8')).trim(); + if (!commitmentText) { + throw new Error('commitment.txt is missing or empty.'); + } + + const prompt = agentModule.getSystemPrompt({ + proposeEnabled: true, + disputeEnabled: true, + commitmentText, + }); + if (!prompt || typeof prompt !== 'string') { + throw new Error('getSystemPrompt() must return a non-empty string.'); + } + + console.log('[agent] Agent module OK:', resolvedPath); + console.log('[agent] commitment.txt length:', commitmentText.length); +} + +main().catch((error) => { + console.error('[agent] validation failed:', error.message ?? error); + process.exit(1); +}); diff --git a/agent/src/index.js b/agent/src/index.js new file mode 100644 index 00000000..f6686ca3 --- /dev/null +++ b/agent/src/index.js @@ -0,0 +1,499 @@ +import dotenv from 'dotenv'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { createPublicClient, http } from 'viem'; +import { buildConfig } from './lib/config.js'; +import { createSignerClient } from './lib/signer.js'; +import { + loadOgContext, + loadOptimisticGovernorDefaults, + logOgFundingStatus, +} from './lib/og.js'; +import { + executeReadyProposals, + pollCommitmentChanges, + pollProposalChanges, + primeBalances, +} from './lib/polling.js'; +import { callAgent, explainToolCalls, parseToolArguments } from './lib/llm.js'; +import { executeToolCalls, toolDefinitions } from './lib/tools.js'; +import { makeDeposit, postBondAndDispute, postBondAndPropose } from './lib/tx.js'; +import { extractTimelockTriggers } from './lib/timelock.js'; +import { collectPriceTriggerSignals } from './lib/uniswapV3Price.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '../..'); + +dotenv.config(); +dotenv.config({ path: path.resolve(repoRoot, 'agent/.env') }); + +const config = buildConfig(); +const publicClient = createPublicClient({ transport: http(config.rpcUrl) }); +const { account, walletClient } = await createSignerClient({ rpcUrl: config.rpcUrl }); +const agentAddress = account.address; + +const trackedAssets = new Set(config.watchAssets.map((asset) => String(asset).toLowerCase())); +let lastCheckedBlock = config.startBlock; +let lastProposalCheckedBlock = config.startBlock; +let lastNativeBalance; +let lastAssetBalances = new Map(); +let ogContext; +const proposalsByHash = new Map(); +const depositHistory = []; +const blockTimestampCache = new Map(); +const timelockTriggers = new Map(); +const priceTriggerState = new Map(); +const tokenMetaCache = new Map(); +const poolMetaCache = new Map(); +const resolvedPoolCache = new Map(); + +async function loadAgentModule() { + const agentRef = config.agentModule ?? 'default'; + const modulePath = agentRef.includes('/') + ? agentRef + : `agent-library/agents/${agentRef}/agent.js`; + const resolvedPath = path.resolve(repoRoot, modulePath); + const moduleUrl = pathToFileURL(resolvedPath).href; + const agentModule = await import(moduleUrl); + + const commitmentPath = path.join(path.dirname(resolvedPath), 'commitment.txt'); + let commitmentText = ''; + try { + commitmentText = (await readFile(commitmentPath, 'utf8')).trim(); + } catch (error) { + console.warn('[agent] Missing commitment.txt next to agent module:', commitmentPath); + } + + return { agentModule, commitmentText }; +} + +const { agentModule, commitmentText } = await loadAgentModule(); +const pollingOptions = (() => { + if (typeof agentModule?.getPollingOptions !== 'function') { + return {}; + } + try { + return agentModule.getPollingOptions({ commitmentText }) ?? {}; + } catch (error) { + console.warn('[agent] getPollingOptions() failed; using defaults.'); + return {}; + } +})(); + +async function getBlockTimestampMs(blockNumber) { + if (!blockNumber) return undefined; + const key = blockNumber.toString(); + if (blockTimestampCache.has(key)) { + return blockTimestampCache.get(key); + } + const block = await publicClient.getBlock({ blockNumber }); + const timestampMs = Number(block.timestamp) * 1000; + blockTimestampCache.set(key, timestampMs); + return timestampMs; +} + +function updateTimelockSchedule({ rulesText }) { + const triggers = extractTimelockTriggers({ + rulesText, + deposits: depositHistory, + }); + + for (const trigger of triggers) { + if (!timelockTriggers.has(trigger.id)) { + timelockTriggers.set(trigger.id, { ...trigger, fired: false }); + } + } +} + +function collectDueTimelocks(nowMs) { + const due = []; + for (const trigger of timelockTriggers.values()) { + if (trigger.fired) continue; + if (trigger.timestampMs <= nowMs) { + due.push(trigger); + } + } + return due; +} + +function markTimelocksFired(triggers) { + for (const trigger of triggers) { + const existing = timelockTriggers.get(trigger.id); + if (existing) { + existing.fired = true; + } + } +} + +async function getActivePriceTriggers({ rulesText }) { + if (typeof agentModule?.getPriceTriggers === 'function') { + try { + const parsed = await agentModule.getPriceTriggers({ + commitmentText: rulesText, + config, + }); + if (Array.isArray(parsed)) { + return parsed; + } + console.warn('[agent] getPriceTriggers() returned non-array; ignoring.'); + return []; + } catch (error) { + console.warn( + '[agent] getPriceTriggers() failed; skipping price triggers:', + error?.message ?? error + ); + return []; + } + } + + return []; +} + +async function decideOnSignals(signals, { onchainPendingProposal = false } = {}) { + if (!config.openAiApiKey) { + return false; + } + + if (!ogContext) { + ogContext = await loadOgContext({ + publicClient, + ogModule: config.ogModule, + }); + } + + const systemPrompt = + agentModule?.getSystemPrompt?.({ + proposeEnabled: config.proposeEnabled, + disputeEnabled: config.disputeEnabled, + commitmentText, + }) ?? + 'You are an agent monitoring an onchain commitment (Safe + Optimistic Governor).'; + + try { + const executableToolsEnabled = + config.proposeEnabled || config.disputeEnabled || config.polymarketClobEnabled; + const tools = toolDefinitions({ + proposeEnabled: config.proposeEnabled, + disputeEnabled: config.disputeEnabled, + clobEnabled: config.polymarketClobEnabled, + onchainToolsEnabled: config.proposeEnabled || config.disputeEnabled, + }); + const allowTools = executableToolsEnabled; + const decision = await callAgent({ + config, + systemPrompt, + signals, + ogContext, + commitmentText, + agentAddress, + tools, + allowTools, + }); + + if (!allowTools && decision?.textDecision) { + console.log('[agent] Opinion:', decision.textDecision); + return true; + } + + if (decision.toolCalls.length > 0) { + let approvedToolCalls = decision.toolCalls; + if (typeof agentModule?.validateToolCalls === 'function') { + try { + const validated = await agentModule.validateToolCalls({ + toolCalls: decision.toolCalls.map((call) => ({ + ...call, + parsedArguments: parseToolArguments(call.arguments), + })), + signals, + commitmentText, + commitmentSafe: config.commitmentSafe, + agentAddress, + publicClient, + config, + onchainPendingProposal, + }); + if (Array.isArray(validated)) { + approvedToolCalls = validated.map((call) => ({ + name: call.name, + callId: call.callId, + arguments: + call.parsedArguments !== undefined + ? JSON.stringify(call.parsedArguments) + : call.arguments !== undefined + ? call.arguments + : JSON.stringify({}), + })); + } else { + approvedToolCalls = []; + } + } catch (error) { + console.warn( + '[agent] validateToolCalls rejected tool calls:', + error?.message ?? error + ); + approvedToolCalls = []; + } + } + + if (approvedToolCalls.length === 0) { + return false; + } + + const toolOutputs = await executeToolCalls({ + toolCalls: approvedToolCalls, + publicClient, + walletClient, + account, + config, + ogContext, + }); + if (toolOutputs.length > 0 && agentModule?.onToolOutput) { + for (const output of toolOutputs) { + if (!output?.name || !output?.output) continue; + let parsed; + try { + parsed = JSON.parse(output.output); + } catch (error) { + parsed = null; + } + await agentModule.onToolOutput({ + name: output.name, + parsedOutput: parsed, + commitmentText, + commitmentSafe: config.commitmentSafe, + agentAddress, + }); + } + } + const modelCallIds = new Set( + approvedToolCalls + .map((call) => call?.callId) + .filter((callId) => typeof callId === 'string' && callId.length > 0) + ); + const explainableOutputs = toolOutputs.filter( + (output) => output?.callId && modelCallIds.has(output.callId) + ); + + if (decision.responseId && explainableOutputs.length > 0) { + const explanation = await explainToolCalls({ + config, + previousResponseId: decision.responseId, + toolOutputs: explainableOutputs, + }); + if (explanation) { + console.log('[agent] Agent explanation:', explanation); + } + } + return true; + } + + if (decision?.textDecision) { + console.log('[agent] Decision:', decision.textDecision); + return true; + } + } catch (error) { + console.error('[agent] Agent call failed', error); + } + + return false; +} + +async function agentLoop() { + try { + const triggerSeedRulesText = ogContext?.rules ?? commitmentText ?? ''; + const triggerSeed = await getActivePriceTriggers({ rulesText: triggerSeedRulesText }); + for (const trigger of triggerSeed) { + if (trigger?.baseToken) trackedAssets.add(String(trigger.baseToken).toLowerCase()); + if (trigger?.quoteToken) trackedAssets.add(String(trigger.quoteToken).toLowerCase()); + } + + const latestBlock = await publicClient.getBlockNumber(); + const latestBlockData = await publicClient.getBlock({ blockNumber: latestBlock }); + const nowMs = Number(latestBlockData.timestamp) * 1000; + + const { + deposits, + balanceSnapshots, + lastCheckedBlock: nextCheckedBlock, + lastNativeBalance: nextNative, + lastAssetBalances: nextAssetBalances, + } = + await pollCommitmentChanges({ + publicClient, + trackedAssets, + commitmentSafe: config.commitmentSafe, + watchNativeBalance: config.watchNativeBalance, + lastCheckedBlock, + lastNativeBalance, + lastAssetBalances, + emitBalanceSnapshotsEveryPoll: Boolean(pollingOptions.emitBalanceSnapshotsEveryPoll), + }); + lastCheckedBlock = nextCheckedBlock; + lastNativeBalance = nextNative; + lastAssetBalances = nextAssetBalances ?? lastAssetBalances; + + for (const deposit of deposits) { + const timestampMs = await getBlockTimestampMs(deposit.blockNumber); + depositHistory.push({ + ...deposit, + timestampMs, + }); + } + + const { + newProposals, + executedProposals, + deletedProposals, + lastProposalCheckedBlock: nextProposalBlock, + } = await pollProposalChanges({ + publicClient, + ogModule: config.ogModule, + lastProposalCheckedBlock, + proposalsByHash, + }); + lastProposalCheckedBlock = nextProposalBlock; + const executedProposalCount = executedProposals?.length ?? 0; + const deletedProposalCount = deletedProposals?.length ?? 0; + if (agentModule?.onProposalEvents) { + agentModule.onProposalEvents({ + executedProposalCount, + deletedProposalCount, + executedProposals, + deletedProposals, + }); + } + if (agentModule?.reconcileProposalSubmission) { + await agentModule.reconcileProposalSubmission({ + publicClient, + ogModule: config.ogModule, + startBlock: config.startBlock, + }); + } + + await executeReadyProposals({ + publicClient, + walletClient, + account, + ogModule: config.ogModule, + proposalsByHash, + executeRetryMs: config.executeRetryMs, + }); + + const rulesText = ogContext?.rules ?? commitmentText ?? ''; + updateTimelockSchedule({ rulesText }); + const dueTimelocks = collectDueTimelocks(nowMs); + const activePriceTriggers = await getActivePriceTriggers({ rulesText }); + const duePriceSignals = await collectPriceTriggerSignals({ + publicClient, + config, + triggers: activePriceTriggers, + nowMs, + triggerState: priceTriggerState, + tokenMetaCache, + poolMetaCache, + resolvedPoolCache, + }); + + const combinedSignals = deposits.concat( + balanceSnapshots, + newProposals.map((proposal) => ({ + kind: 'proposal', + proposalHash: proposal.proposalHash, + assertionId: proposal.assertionId, + proposer: proposal.proposer, + challengeWindowEnds: proposal.challengeWindowEnds, + transactions: proposal.transactions, + rules: proposal.rules, + explanation: proposal.explanation, + })) + ); + + for (const trigger of dueTimelocks) { + combinedSignals.push({ + kind: 'timelock', + triggerId: trigger.id, + triggerTimestampMs: trigger.timestampMs, + source: trigger.source, + anchor: trigger.anchor, + deposit: trigger.deposit, + }); + } + combinedSignals.push(...duePriceSignals); + + // Allow agent module to augment signals (e.g., add timer signals) + let signalsToProcess = combinedSignals; + if (agentModule?.augmentSignals) { + signalsToProcess = agentModule.augmentSignals(combinedSignals, { + nowMs, + latestBlock, + }); + } + if (agentModule?.enrichSignals) { + try { + signalsToProcess = await agentModule.enrichSignals(signalsToProcess, { + publicClient, + config, + account, + onchainPendingProposal: proposalsByHash.size > 0, + nowMs, + latestBlock, + }); + } catch (error) { + console.error('[agent] Failed to enrich signals:', error); + } + } + + if (signalsToProcess.length > 0) { + const decisionOk = await decideOnSignals(signalsToProcess, { + onchainPendingProposal: proposalsByHash.size > 0, + }); + if (decisionOk && dueTimelocks.length > 0) { + markTimelocksFired(dueTimelocks); + } + } + } catch (error) { + console.error('[agent] loop error', error); + } + + setTimeout(agentLoop, config.pollIntervalMs); +} + +async function startAgent() { + await loadOptimisticGovernorDefaults({ + publicClient, + ogModule: config.ogModule, + trackedAssets, + }); + + ogContext = await loadOgContext({ publicClient, ogModule: config.ogModule }); + await logOgFundingStatus({ publicClient, ogModule: config.ogModule, account }); + + if (lastCheckedBlock === undefined) { + lastCheckedBlock = await publicClient.getBlockNumber(); + } + if (lastProposalCheckedBlock === undefined) { + lastProposalCheckedBlock = lastCheckedBlock; + } + + lastNativeBalance = await primeBalances({ + publicClient, + commitmentSafe: config.commitmentSafe, + watchNativeBalance: config.watchNativeBalance, + blockNumber: lastCheckedBlock, + }); + + console.log('[agent] running...'); + + agentLoop(); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + startAgent().catch((error) => { + console.error('[agent] failed to start', error); + process.exit(1); + }); +} + +export { makeDeposit, postBondAndDispute, postBondAndPropose, startAgent }; diff --git a/agent/src/lib/config.js b/agent/src/lib/config.js new file mode 100644 index 00000000..f20187d9 --- /dev/null +++ b/agent/src/lib/config.js @@ -0,0 +1,139 @@ +import { getAddress } from 'viem'; +import { mustGetEnv, parseAddressList } from './utils.js'; + +function parseFeeTierList(raw) { + if (!raw) return [500, 3000, 10000]; + const values = raw + .split(',') + .map((value) => value.trim()) + .filter(Boolean) + .map((value) => Number(value)); + if (values.some((value) => !Number.isInteger(value) || value <= 0)) { + throw new Error('UNISWAP_V3_FEE_TIERS must be comma-separated positive integers'); + } + return values; +} + +function buildConfig() { + return { + rpcUrl: mustGetEnv('RPC_URL'), + commitmentSafe: getAddress(mustGetEnv('COMMITMENT_SAFE')), + ogModule: getAddress(mustGetEnv('OG_MODULE')), + pollIntervalMs: Number(process.env.POLL_INTERVAL_MS ?? 10_000), + startBlock: process.env.START_BLOCK ? BigInt(process.env.START_BLOCK) : undefined, + watchAssets: parseAddressList(process.env.WATCH_ASSETS), + watchNativeBalance: + process.env.WATCH_NATIVE_BALANCE === undefined + ? true + : process.env.WATCH_NATIVE_BALANCE.toLowerCase() !== 'false', + defaultDepositAsset: process.env.DEFAULT_DEPOSIT_ASSET + ? getAddress(process.env.DEFAULT_DEPOSIT_ASSET) + : undefined, + defaultDepositAmountWei: process.env.DEFAULT_DEPOSIT_AMOUNT_WEI + ? BigInt(process.env.DEFAULT_DEPOSIT_AMOUNT_WEI) + : undefined, + bondSpender: (process.env.BOND_SPENDER ?? 'og').toLowerCase(), + openAiApiKey: process.env.OPENAI_API_KEY, + openAiModel: process.env.OPENAI_MODEL ?? 'gpt-4.1-mini', + openAiBaseUrl: process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1', + allowProposeOnSimulationFail: + process.env.ALLOW_PROPOSE_ON_SIMULATION_FAIL === undefined + ? false + : process.env.ALLOW_PROPOSE_ON_SIMULATION_FAIL.toLowerCase() === 'true', + proposeGasLimit: process.env.PROPOSE_GAS_LIMIT + ? BigInt(process.env.PROPOSE_GAS_LIMIT) + : 2_000_000n, + executeRetryMs: Number(process.env.EXECUTE_RETRY_MS ?? 60_000), + proposeEnabled: + process.env.PROPOSE_ENABLED === undefined + ? true + : process.env.PROPOSE_ENABLED.toLowerCase() !== 'false', + disputeEnabled: + process.env.DISPUTE_ENABLED === undefined + ? true + : process.env.DISPUTE_ENABLED.toLowerCase() !== 'false', + disputeRetryMs: Number(process.env.DISPUTE_RETRY_MS ?? 60_000), + agentModule: process.env.AGENT_MODULE, + chainlinkPriceFeed: process.env.CHAINLINK_PRICE_FEED + ? getAddress(process.env.CHAINLINK_PRICE_FEED) + : undefined, + polymarketConditionalTokens: process.env.POLYMARKET_CONDITIONAL_TOKENS + ? getAddress(process.env.POLYMARKET_CONDITIONAL_TOKENS) + : getAddress('0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'), + polymarketExchange: process.env.POLYMARKET_EXCHANGE + ? getAddress(process.env.POLYMARKET_EXCHANGE) + : undefined, + polymarketClobEnabled: + process.env.POLYMARKET_CLOB_ENABLED === undefined + ? false + : process.env.POLYMARKET_CLOB_ENABLED.toLowerCase() !== 'false', + polymarketClobHost: process.env.POLYMARKET_CLOB_HOST ?? 'https://clob.polymarket.com', + polymarketClobAddress: process.env.POLYMARKET_CLOB_ADDRESS + ? getAddress(process.env.POLYMARKET_CLOB_ADDRESS) + : undefined, + polymarketClobSignatureType: process.env.POLYMARKET_CLOB_SIGNATURE_TYPE, + polymarketClobApiKey: process.env.POLYMARKET_CLOB_API_KEY, + polymarketClobApiSecret: process.env.POLYMARKET_CLOB_API_SECRET, + polymarketClobApiPassphrase: process.env.POLYMARKET_CLOB_API_PASSPHRASE, + polymarketClobRequestTimeoutMs: Number( + process.env.POLYMARKET_CLOB_REQUEST_TIMEOUT_MS ?? 15_000 + ), + polymarketClobMaxRetries: Number(process.env.POLYMARKET_CLOB_MAX_RETRIES ?? 1), + polymarketClobRetryDelayMs: Number(process.env.POLYMARKET_CLOB_RETRY_DELAY_MS ?? 250), + polymarketRelayerEnabled: + process.env.POLYMARKET_RELAYER_ENABLED === undefined + ? false + : process.env.POLYMARKET_RELAYER_ENABLED.toLowerCase() !== 'false', + polymarketRelayerHost: + process.env.POLYMARKET_RELAYER_HOST ?? 'https://relayer-v2.polymarket.com', + polymarketRelayerTxType: process.env.POLYMARKET_RELAYER_TX_TYPE ?? 'SAFE', + polymarketRelayerFromAddress: process.env.POLYMARKET_RELAYER_FROM_ADDRESS + ? getAddress(process.env.POLYMARKET_RELAYER_FROM_ADDRESS) + : undefined, + polymarketRelayerSafeFactory: process.env.POLYMARKET_RELAYER_SAFE_FACTORY + ? getAddress(process.env.POLYMARKET_RELAYER_SAFE_FACTORY) + : undefined, + polymarketRelayerProxyFactory: process.env.POLYMARKET_RELAYER_PROXY_FACTORY + ? getAddress(process.env.POLYMARKET_RELAYER_PROXY_FACTORY) + : undefined, + polymarketRelayerResolveProxyAddress: + process.env.POLYMARKET_RELAYER_RESOLVE_PROXY_ADDRESS === undefined + ? true + : process.env.POLYMARKET_RELAYER_RESOLVE_PROXY_ADDRESS.toLowerCase() !== + 'false', + polymarketRelayerAutoDeployProxy: + process.env.POLYMARKET_RELAYER_AUTO_DEPLOY_PROXY === undefined + ? false + : process.env.POLYMARKET_RELAYER_AUTO_DEPLOY_PROXY.toLowerCase() === 'true', + polymarketRelayerChainId: process.env.POLYMARKET_RELAYER_CHAIN_ID + ? Number(process.env.POLYMARKET_RELAYER_CHAIN_ID) + : undefined, + polymarketRelayerRequestTimeoutMs: Number( + process.env.POLYMARKET_RELAYER_REQUEST_TIMEOUT_MS ?? 15_000 + ), + polymarketRelayerPollIntervalMs: Number( + process.env.POLYMARKET_RELAYER_POLL_INTERVAL_MS ?? 2_000 + ), + polymarketRelayerPollTimeoutMs: Number( + process.env.POLYMARKET_RELAYER_POLL_TIMEOUT_MS ?? 120_000 + ), + polymarketApiKey: process.env.POLYMARKET_API_KEY, + polymarketApiSecret: process.env.POLYMARKET_API_SECRET, + polymarketApiPassphrase: process.env.POLYMARKET_API_PASSPHRASE, + polymarketBuilderApiKey: + process.env.POLYMARKET_BUILDER_API_KEY ?? process.env.POLYMARKET_API_KEY, + polymarketBuilderSecret: + process.env.POLYMARKET_BUILDER_SECRET ?? process.env.POLYMARKET_API_SECRET, + polymarketBuilderPassphrase: + process.env.POLYMARKET_BUILDER_PASSPHRASE ?? process.env.POLYMARKET_API_PASSPHRASE, + uniswapV3Factory: process.env.UNISWAP_V3_FACTORY + ? getAddress(process.env.UNISWAP_V3_FACTORY) + : undefined, + uniswapV3Quoter: process.env.UNISWAP_V3_QUOTER + ? getAddress(process.env.UNISWAP_V3_QUOTER) + : undefined, + uniswapV3FeeTiers: parseFeeTierList(process.env.UNISWAP_V3_FEE_TIERS), + }; +} + +export { buildConfig }; diff --git a/agent/src/lib/llm.js b/agent/src/lib/llm.js new file mode 100644 index 00000000..b41f5550 --- /dev/null +++ b/agent/src/lib/llm.js @@ -0,0 +1,201 @@ +import { parseToolArguments } from './utils.js'; + +function extractFirstText(responseJson) { + const outputs = responseJson?.output; + if (!Array.isArray(outputs)) return ''; + + for (const item of outputs) { + if (!item?.content) continue; + for (const chunk of item.content) { + if (chunk?.text) return chunk.text; + if (chunk?.output_text) return chunk.output_text?.text ?? ''; + if (chunk?.text?.value) return chunk.text.value; + } + } + + return ''; +} + +function extractToolCalls(responseJson) { + const outputs = responseJson?.output; + if (!Array.isArray(outputs)) return []; + + const toolCalls = []; + for (const item of outputs) { + if (item?.type === 'tool_call' || item?.type === 'function_call') { + toolCalls.push({ + name: item?.name ?? item?.function?.name, + arguments: item?.arguments ?? item?.function?.arguments, + callId: item?.call_id ?? item?.id, + }); + continue; + } + + if (Array.isArray(item?.tool_calls)) { + for (const call of item.tool_calls) { + toolCalls.push({ + name: call?.name ?? call?.function?.name, + arguments: call?.arguments ?? call?.function?.arguments, + callId: call?.call_id ?? call?.id, + }); + } + } + } + + return toolCalls.filter((call) => call.name); +} + +async function callAgent({ + config, + systemPrompt, + signals, + ogContext, + commitmentText, + agentAddress, + tools, + allowTools, +}) { + const safeSignals = signals.map((signal) => { + if (signal?.kind === 'proposal') { + return { + ...signal, + challengeWindowEnds: + signal.challengeWindowEnds !== undefined + ? signal.challengeWindowEnds.toString() + : undefined, + transactions: Array.isArray(signal.transactions) + ? signal.transactions.map((tx) => ({ + ...tx, + value: tx.value !== undefined ? tx.value.toString() : undefined, + })) + : undefined, + }; + } + + return { + ...signal, + amount: signal.amount !== undefined ? signal.amount.toString() : undefined, + blockNumber: signal.blockNumber !== undefined ? signal.blockNumber.toString() : undefined, + transactionHash: signal.transactionHash ? String(signal.transactionHash) : undefined, + timestampMs: + signal.timestampMs !== undefined ? signal.timestampMs.toString() : undefined, + triggerTimestampMs: + signal.triggerTimestampMs !== undefined + ? signal.triggerTimestampMs.toString() + : undefined, + threshold: + signal.threshold !== undefined ? String(signal.threshold) : undefined, + observedPrice: + signal.observedPrice !== undefined ? String(signal.observedPrice) : undefined, + poolFee: signal.poolFee !== undefined ? String(signal.poolFee) : undefined, + }; + }); + + const safeContext = { + rules: ogContext?.rules, + identifier: ogContext?.identifier ? String(ogContext.identifier) : undefined, + liveness: ogContext?.liveness !== undefined ? ogContext.liveness.toString() : undefined, + collateral: ogContext?.collateral, + bondAmount: ogContext?.bondAmount !== undefined ? ogContext.bondAmount.toString() : undefined, + optimisticOracle: ogContext?.optimisticOracle, + }; + + const payload = { + model: config.openAiModel, + input: [ + { + role: 'system', + content: systemPrompt, + }, + { + role: 'user', + content: JSON.stringify( + { + commitmentSafe: config.commitmentSafe, + ogModule: config.ogModule, + agentAddress, + ogContext: safeContext, + commitment: commitmentText, + signals: safeSignals, + }, + (_, value) => (typeof value === 'bigint' ? value.toString() : value) + ), + }, + ], + tools: allowTools ? tools : [], + tool_choice: allowTools ? 'auto' : 'none', + parallel_tool_calls: false, + text: { format: { type: 'json_object' } }, + }; + + const res = await fetch(`${config.openAiBaseUrl}/responses`, { + method: 'POST', + headers: { + Authorization: `Bearer ${config.openAiApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`OpenAI API error: ${res.status} ${text}`); + } + + const json = await res.json(); + const toolCalls = allowTools ? extractToolCalls(json) : []; + const raw = extractFirstText(json); + let textDecision; + if (raw) { + try { + textDecision = JSON.parse(raw); + } catch (error) { + throw new Error(`Failed to parse OpenAI JSON: ${raw}`); + } + } + + return { toolCalls, textDecision, responseId: json?.id }; +} + +async function explainToolCalls({ config, previousResponseId, toolOutputs }) { + const input = [ + ...toolOutputs.map((item) => ({ + type: 'function_call_output', + call_id: item.callId, + output: item.output, + })), + { + type: 'message', + role: 'user', + content: [ + { + type: 'input_text', + text: 'Summarize the actions you took and why.', + }, + ], + }, + ]; + + const res = await fetch(`${config.openAiBaseUrl}/responses`, { + method: 'POST', + headers: { + Authorization: `Bearer ${config.openAiApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: config.openAiModel, + previous_response_id: previousResponseId, + input, + }), + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`OpenAI API error: ${res.status} ${text}`); + } + + const json = await res.json(); + return extractFirstText(json); +} + +export { callAgent, explainToolCalls, extractToolCalls, extractFirstText, parseToolArguments }; diff --git a/agent/src/lib/og.js b/agent/src/lib/og.js new file mode 100644 index 00000000..89af0ca3 --- /dev/null +++ b/agent/src/lib/og.js @@ -0,0 +1,181 @@ +import { erc20Abi, getAddress, parseAbi, parseAbiItem, stringToHex } from 'viem'; + +const optimisticGovernorAbi = parseAbi([ + 'function proposeTransactions((address to,uint8 operation,uint256 value,bytes data)[] transactions, bytes explanation)', + 'function executeProposal((address to,uint8 operation,uint256 value,bytes data)[] transactions)', + 'function collateral() view returns (address)', + 'function bondAmount() view returns (uint256)', + 'function optimisticOracleV3() view returns (address)', + 'function rules() view returns (string)', + 'function identifier() view returns (bytes32)', + 'function liveness() view returns (uint64)', + 'function assertionIds(bytes32) view returns (bytes32)', +]); + +const optimisticOracleAbi = parseAbi([ + 'function disputeAssertion(bytes32 assertionId, address disputer)', + 'function getMinimumBond(address collateral) view returns (uint256)', + 'function getAssertion(bytes32 assertionId) view returns ((bool arbitrateViaEscalationManager,bool discardOracle,bool validateDisputers,address assertingCaller,address escalationManager) escalationManagerSettings,address asserter,uint64 assertionTime,bool settled,address currency,uint64 expirationTime,bool settlementResolution,bytes32 domainId,bytes32 identifier,uint256 bond,address callbackRecipient,address disputer)', +]); + +const transferEvent = parseAbiItem( + 'event Transfer(address indexed from, address indexed to, uint256 value)' +); +const transactionsProposedEvent = parseAbiItem( + 'event TransactionsProposed(address indexed proposer,uint256 indexed proposalTime,bytes32 indexed assertionId,((address to,uint8 operation,uint256 value,bytes data)[] transactions,uint256 requestTime) proposal,bytes32 proposalHash,bytes explanation,string rules,uint256 challengeWindowEnds)' +); +const proposalExecutedEvent = parseAbiItem( + 'event ProposalExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId)' +); +const proposalDeletedEvent = parseAbiItem( + 'event ProposalDeleted(bytes32 indexed proposalHash, bytes32 indexed assertionId)' +); + +async function loadOptimisticGovernorDefaults({ publicClient, ogModule, trackedAssets }) { + const collateral = await publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'collateral', + }); + + trackedAssets.add(getAddress(collateral).toLowerCase()); +} + +async function loadOgContext({ publicClient, ogModule }) { + const [collateral, bondAmount, optimisticOracle, rules, identifier, liveness] = + await Promise.all([ + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'collateral', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'bondAmount', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'optimisticOracleV3', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'rules', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'identifier', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'liveness', + }), + ]); + + return { + collateral, + bondAmount, + optimisticOracle, + rules, + identifier, + liveness, + }; +} + +async function logOgFundingStatus({ publicClient, ogModule, account }) { + try { + const chainId = await publicClient.getChainId(); + const expectedIdentifierStr = + chainId === 11155111 ? 'ASSERT_TRUTH2' : 'ASSERT_TRUTH2'; + const expectedIdentifier = stringToHex(expectedIdentifierStr, { size: 32 }); + + const [collateral, bondAmount, optimisticOracle, identifier] = await Promise.all([ + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'collateral', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'bondAmount', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'optimisticOracleV3', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'identifier', + }), + ]); + const minimumBond = await publicClient.readContract({ + address: optimisticOracle, + abi: optimisticOracleAbi, + functionName: 'getMinimumBond', + args: [collateral], + }); + + const requiredBond = bondAmount > minimumBond ? bondAmount : minimumBond; + const collateralBalance = await publicClient.readContract({ + address: collateral, + abi: erc20Abi, + functionName: 'balanceOf', + args: [account.address], + }); + const nativeBalance = await publicClient.getBalance({ address: account.address }); + + if (identifier !== expectedIdentifier) { + console.warn( + `[agent] OG identifier mismatch: expected ${expectedIdentifierStr}, onchain ${identifier}` + ); + } + void requiredBond; + void collateralBalance; + void nativeBalance; + } catch (error) { + console.warn('[agent] Failed to log OG funding status:', error); + } +} + +function normalizeAssertion(assertion) { + if (!assertion) return {}; + if (typeof assertion === 'object' && !Array.isArray(assertion)) { + return assertion; + } + + const tuple = Array.isArray(assertion) ? assertion : []; + return { + escalationManagerSettings: tuple[0], + asserter: tuple[1], + assertionTime: tuple[2], + settled: tuple[3], + currency: tuple[4], + expirationTime: tuple[5], + settlementResolution: tuple[6], + domainId: tuple[7], + identifier: tuple[8], + bond: tuple[9], + callbackRecipient: tuple[10], + disputer: tuple[11], + }; +} + +export { + optimisticGovernorAbi, + optimisticOracleAbi, + transferEvent, + transactionsProposedEvent, + proposalExecutedEvent, + proposalDeletedEvent, + loadOptimisticGovernorDefaults, + loadOgContext, + logOgFundingStatus, + normalizeAssertion, +}; diff --git a/agent/src/lib/polling.js b/agent/src/lib/polling.js new file mode 100644 index 00000000..39825f0f --- /dev/null +++ b/agent/src/lib/polling.js @@ -0,0 +1,445 @@ +import { erc20Abi, getAddress, hexToString, isAddressEqual, zeroAddress } from 'viem'; +import { + optimisticGovernorAbi, + proposalDeletedEvent, + proposalExecutedEvent, + transactionsProposedEvent, + transferEvent, +} from './og.js'; + +function getAlwaysEmitBalanceSnapshotPollingOptions() { + return { + emitBalanceSnapshotsEveryPoll: true, + }; +} + +async function primeBalances({ publicClient, commitmentSafe, watchNativeBalance, blockNumber }) { + if (!watchNativeBalance) return undefined; + + return publicClient.getBalance({ + address: commitmentSafe, + blockNumber, + }); +} + +async function primeAssetBalanceSignals({ publicClient, trackedAssets, commitmentSafe, blockNumber }) { + const balances = await Promise.all( + Array.from(trackedAssets).map(async (asset) => { + if (isAddressEqual(asset, zeroAddress)) { + return { asset, balance: 0n }; + } + const balance = await publicClient.readContract({ + address: asset, + abi: erc20Abi, + functionName: 'balanceOf', + args: [commitmentSafe], + blockNumber, + }); + return { asset, balance }; + }) + ); + + const signals = balances + .filter((item) => item.balance > 0n) + .map((item) => ({ + kind: 'erc20BalanceSnapshot', + asset: item.asset, + from: 'snapshot', + amount: item.balance, + blockNumber, + transactionHash: undefined, + logIndex: undefined, + id: `snapshot:${item.asset}:${blockNumber.toString()}`, + })); + + const balanceMap = new Map(balances.map((item) => [item.asset, item.balance])); + return { signals, balanceMap }; +} + +async function collectAssetBalanceChangeSignals({ + publicClient, + trackedAssets, + commitmentSafe, + blockNumber, + lastAssetBalances, + emitBalanceSnapshotsEveryPoll = false, +}) { + const nextAssetBalances = new Map(lastAssetBalances ?? []); + const signals = []; + + for (const asset of trackedAssets) { + if (isAddressEqual(asset, zeroAddress)) { + continue; + } + const current = await publicClient.readContract({ + address: asset, + abi: erc20Abi, + functionName: 'balanceOf', + args: [commitmentSafe], + blockNumber, + }); + const previous = nextAssetBalances.get(asset); + nextAssetBalances.set(asset, current); + + const hasChanged = previous !== undefined && current !== previous; + const isFirstObservationNonZero = previous === undefined && current > 0n; + const shouldEmit = emitBalanceSnapshotsEveryPoll + ? current > 0n + : hasChanged || isFirstObservationNonZero; + if (shouldEmit) { + signals.push({ + kind: 'erc20BalanceSnapshot', + asset, + from: 'snapshot', + amount: current, + blockNumber, + transactionHash: undefined, + logIndex: undefined, + id: `snapshot:${asset}:${blockNumber.toString()}`, + }); + } + } + + return { signals, nextAssetBalances }; +} + +async function pollCommitmentChanges({ + publicClient, + trackedAssets, + commitmentSafe, + watchNativeBalance, + lastCheckedBlock, + lastNativeBalance, + lastAssetBalances, + emitBalanceSnapshotsEveryPoll = false, +}) { + const latestBlock = await publicClient.getBlockNumber(); + if (lastCheckedBlock === undefined) { + const nextNativeBalance = await primeBalances({ + publicClient, + commitmentSafe, + watchNativeBalance, + blockNumber: latestBlock, + }); + const { signals: initialAssetSignals, balanceMap: initialAssetBalanceMap } = + await primeAssetBalanceSignals({ + publicClient, + trackedAssets, + commitmentSafe, + blockNumber: latestBlock, + }); + if (initialAssetSignals.length > 0) { + console.log( + `[agent] Startup balance snapshot signals: ${initialAssetSignals + .map((s) => `${s.asset}:${s.amount.toString()}`) + .join(', ')}` + ); + } + return { + deposits: [], + balanceSnapshots: initialAssetSignals, + lastCheckedBlock: latestBlock, + lastNativeBalance: nextNativeBalance, + lastAssetBalances: + lastAssetBalances ?? initialAssetBalanceMap, + }; + } + + if (latestBlock <= lastCheckedBlock) { + return { + deposits: [], + balanceSnapshots: [], + lastCheckedBlock, + lastNativeBalance, + lastAssetBalances, + }; + } + + const fromBlock = lastCheckedBlock + 1n; + const toBlock = latestBlock; + const deposits = []; + + const maxRange = 10n; + let currentFrom = fromBlock; + while (currentFrom <= toBlock) { + const currentTo = + currentFrom + maxRange - 1n > toBlock ? toBlock : currentFrom + maxRange - 1n; + + for (const asset of trackedAssets) { + if (isAddressEqual(asset, zeroAddress)) { + continue; + } + const logs = await publicClient.getLogs({ + address: asset, + event: transferEvent, + args: { to: commitmentSafe }, + fromBlock: currentFrom, + toBlock: currentTo, + }); + + for (const log of logs) { + deposits.push({ + kind: 'erc20Deposit', + asset, + from: log.args.from, + amount: log.args.value, + blockNumber: log.blockNumber, + transactionHash: log.transactionHash, + logIndex: log.logIndex, + id: log.transactionHash + ? `${log.transactionHash}:${log.logIndex ?? '0'}` + : `${log.blockNumber.toString()}:${log.logIndex ?? '0'}`, + }); + } + } + + currentFrom = currentTo + 1n; + } + + let nextNativeBalance = lastNativeBalance; + if (watchNativeBalance) { + const nativeBalance = await publicClient.getBalance({ + address: commitmentSafe, + blockNumber: toBlock, + }); + + if (lastNativeBalance !== undefined && nativeBalance > lastNativeBalance) { + deposits.push({ + kind: 'nativeDeposit', + asset: zeroAddress, + from: 'unknown', + amount: nativeBalance - lastNativeBalance, + blockNumber: toBlock, + transactionHash: undefined, + logIndex: undefined, + id: `native:${toBlock.toString()}:${(nativeBalance - lastNativeBalance).toString()}`, + }); + } + + nextNativeBalance = nativeBalance; + } + + const { signals: balanceSnapshots, nextAssetBalances } = await collectAssetBalanceChangeSignals({ + publicClient, + trackedAssets, + commitmentSafe, + blockNumber: toBlock, + lastAssetBalances, + emitBalanceSnapshotsEveryPoll, + }); + + return { + deposits, + balanceSnapshots, + lastCheckedBlock: toBlock, + lastNativeBalance: nextNativeBalance, + lastAssetBalances: nextAssetBalances, + }; +} + +async function pollProposalChanges({ publicClient, ogModule, lastProposalCheckedBlock, proposalsByHash }) { + const latestBlock = await publicClient.getBlockNumber(); + if (lastProposalCheckedBlock === undefined) { + return { + newProposals: [], + executedProposals: [], + deletedProposals: [], + lastProposalCheckedBlock: latestBlock, + }; + } + + if (latestBlock <= lastProposalCheckedBlock) { + return { + newProposals: [], + executedProposals: [], + deletedProposals: [], + lastProposalCheckedBlock, + }; + } + + const fromBlock = lastProposalCheckedBlock + 1n; + const toBlock = latestBlock; + + const maxRange = 10n; + const proposedLogs = []; + const executedLogs = []; + const deletedLogs = []; + let currentFrom = fromBlock; + while (currentFrom <= toBlock) { + const currentTo = + currentFrom + maxRange - 1n > toBlock ? toBlock : currentFrom + maxRange - 1n; + + const [chunkProposed, chunkExecuted, chunkDeleted] = await Promise.all([ + publicClient.getLogs({ + address: ogModule, + event: transactionsProposedEvent, + fromBlock: currentFrom, + toBlock: currentTo, + }), + publicClient.getLogs({ + address: ogModule, + event: proposalExecutedEvent, + fromBlock: currentFrom, + toBlock: currentTo, + }), + publicClient.getLogs({ + address: ogModule, + event: proposalDeletedEvent, + fromBlock: currentFrom, + toBlock: currentTo, + }), + ]); + + proposedLogs.push(...chunkProposed); + executedLogs.push(...chunkExecuted); + deletedLogs.push(...chunkDeleted); + + currentFrom = currentTo + 1n; + } + + const newProposals = []; + for (const log of proposedLogs) { + const proposalHash = log.args?.proposalHash; + const assertionId = log.args?.assertionId; + const proposal = log.args?.proposal; + const challengeWindowEnds = log.args?.challengeWindowEnds; + if (!proposalHash || !proposal?.transactions) continue; + const proposer = log.args?.proposer; + const explanationHex = log.args?.explanation; + const rules = log.args?.rules; + let explanation; + if (explanationHex && typeof explanationHex === 'string') { + if (explanationHex.startsWith('0x')) { + try { + explanation = hexToString(explanationHex); + } catch (error) { + explanation = undefined; + } + } else { + explanation = explanationHex; + } + } + + const transactions = proposal.transactions.map((tx) => ({ + to: getAddress(tx.to), + operation: Number(tx.operation ?? 0), + value: BigInt(tx.value ?? 0), + data: tx.data ?? '0x', + })); + + const proposalRecord = { + proposalHash, + assertionId, + proposer: proposer ? getAddress(proposer) : undefined, + challengeWindowEnds: BigInt(challengeWindowEnds ?? 0), + transactions, + lastAttemptMs: 0, + disputeAttemptMs: 0, + rules, + explanation, + }; + proposalsByHash.set(proposalHash, proposalRecord); + newProposals.push(proposalRecord); + } + + const executedProposals = []; + for (const log of executedLogs) { + const proposalHash = log.args?.proposalHash; + if (proposalHash) { + proposalsByHash.delete(proposalHash); + executedProposals.push(proposalHash); + } + } + + const deletedProposals = []; + for (const log of deletedLogs) { + const proposalHash = log.args?.proposalHash; + if (proposalHash) { + proposalsByHash.delete(proposalHash); + deletedProposals.push(proposalHash); + } + } + + return { newProposals, executedProposals, deletedProposals, lastProposalCheckedBlock: toBlock }; +} + +async function executeReadyProposals({ + publicClient, + walletClient, + account, + ogModule, + proposalsByHash, + executeRetryMs, +}) { + if (proposalsByHash.size === 0) return; + + const latestBlock = await publicClient.getBlockNumber(); + const block = await publicClient.getBlock({ blockNumber: latestBlock }); + const now = BigInt(block.timestamp); + const nowMs = Date.now(); + + for (const proposal of proposalsByHash.values()) { + if (!proposal?.transactions?.length) continue; + if (proposal.challengeWindowEnds === undefined) continue; + if (now < proposal.challengeWindowEnds) continue; + if (proposal.lastAttemptMs && nowMs - proposal.lastAttemptMs < executeRetryMs) { + continue; + } + + proposal.lastAttemptMs = nowMs; + + let assertionId; + try { + assertionId = await publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'assertionIds', + args: [proposal.proposalHash], + }); + } catch (error) { + console.warn('[agent] Failed to read assertionId:', error); + continue; + } + + if (!assertionId || assertionId === `0x${'0'.repeat(64)}`) { + proposalsByHash.delete(proposal.proposalHash); + continue; + } + + try { + await publicClient.simulateContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'executeProposal', + args: [proposal.transactions], + account: account.address, + }); + } catch (error) { + const reason = error?.shortMessage ?? error?.message ?? String(error); + console.warn( + `[agent] Proposal execution simulation failed for ${proposal.proposalHash}: ${reason}` + ); + continue; + } + + try { + const txHash = await walletClient.writeContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'executeProposal', + args: [proposal.transactions], + }); + console.log('[agent] Proposal execution submitted:', txHash); + } catch (error) { + console.warn('[agent] Proposal execution failed:', error?.shortMessage ?? error?.message ?? error); + } + } +} + +export { + primeBalances, + getAlwaysEmitBalanceSnapshotPollingOptions, + pollCommitmentChanges, + pollProposalChanges, + executeReadyProposals, +}; diff --git a/agent/src/lib/polymarket-relayer.js b/agent/src/lib/polymarket-relayer.js new file mode 100644 index 00000000..06909fa4 --- /dev/null +++ b/agent/src/lib/polymarket-relayer.js @@ -0,0 +1,1260 @@ +import crypto from 'node:crypto'; +import { + encodeAbiParameters, + encodePacked, + getAddress, + getCreate2Address, + hashTypedData, + isHex, + keccak256, + zeroAddress, +} from 'viem'; +import { normalizeAddressOrNull, normalizeHashOrNull } from './utils.js'; + +const DEFAULT_RELAYER_HOST = 'https://relayer-v2.polymarket.com'; +const DEFAULT_RELAYER_REQUEST_TIMEOUT_MS = 15_000; +const DEFAULT_RELAYER_POLL_INTERVAL_MS = 2_000; +const DEFAULT_RELAYER_POLL_TIMEOUT_MS = 120_000; +const SAFE_FACTORY_ADDRESS = '0xaacfeea03eb1561c4e67d661e40682bd20e3541b'; +const PROXY_FACTORY_ADDRESS = SAFE_FACTORY_ADDRESS; +const SAFE_INIT_CODE_HASH = + '0xb61d27f6f0f1579b6af9d23fafd567586f35f7d2f43d6bd5f85c0b690952d469'; +const PROXY_INIT_CODE_HASH = + '0x72ea4f5319066fd7435f2f2e1e8f117d0848fa51987edc76b4e2207ee3f1fe6f'; +const CREATE_PROXY_DOMAIN_NAME = 'Polymarket Contract Proxy Factory'; + +const SAFE_EIP712_TYPES = Object.freeze({ + EIP712Domain: [ + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + SafeTx: [ + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'operation', type: 'uint8' }, + { name: 'safeTxGas', type: 'uint256' }, + { name: 'baseGas', type: 'uint256' }, + { name: 'gasPrice', type: 'uint256' }, + { name: 'gasToken', type: 'address' }, + { name: 'refundReceiver', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + ], +}); + +const CREATE_PROXY_EIP712_TYPES = Object.freeze({ + CreateProxy: [ + { name: 'paymentToken', type: 'address' }, + { name: 'payment', type: 'uint256' }, + { name: 'paymentReceiver', type: 'address' }, + ], +}); + +const RELAYER_TX_TYPE = Object.freeze({ + SAFE: 'SAFE', + PROXY: 'PROXY', +}); + +const RELAYER_ENDPOINTS = Object.freeze({ + ADDRESS: '/address', + NONCE: '/nonce', + RELAY_PAYLOAD: '/relay-payload', + SUBMIT: '/submit', + TRANSACTION: '/transaction', + DEPLOYED: '/deployed', +}); + +const LEGACY_ENDPOINTS = Object.freeze({ + PROXY_ADDRESS: (address) => `/relayer/proxy-address/${encodeURIComponent(address)}`, + PROXY_NONCE: (address) => `/relayer/proxy-nonce/${encodeURIComponent(address)}`, + CREATE_PROXY: '/relayer/create-proxy-wallet', + TRANSACTION: '/relayer/transaction', + TRANSACTION_STATUS: (hash) => `/relayer/transaction-status/${encodeURIComponent(hash)}`, +}); + +const RELAYER_SUCCESS_STATES = new Set([ + 'STATE_MINED', + 'STATE_CONFIRMED', + 'MINED', + 'CONFIRMED', +]); + +const RELAYER_FAILURE_STATES = new Set([ + 'STATE_FAILED', + 'STATE_INVALID', + 'FAILED', + 'REVERTED', + 'INVALID', +]); + +function normalizeNonNegativeInteger(value, fallback) { + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed < 0) return fallback; + return Math.floor(parsed); +} + +function normalizeRelayerHost(host) { + return (host ?? DEFAULT_RELAYER_HOST).replace(/\/+$/, ''); +} + +function normalizeRelayerTxType(value) { + if (typeof value !== 'string') { + return RELAYER_TX_TYPE.SAFE; + } + const normalized = value.trim().toUpperCase(); + if (normalized === RELAYER_TX_TYPE.PROXY) return RELAYER_TX_TYPE.PROXY; + return RELAYER_TX_TYPE.SAFE; +} + +function normalizeHexData(data) { + if (!data) return '0x'; + if (typeof data !== 'string' || !isHex(data)) { + throw new Error('Relayer transaction data must be a hex string.'); + } + return data; +} + +function normalizeMetadata(metadata) { + if (typeof metadata === 'string') { + return metadata; + } + if (metadata === null || metadata === undefined) { + return ''; + } + try { + return JSON.stringify(metadata); + } catch (error) { + return String(metadata); + } +} + +function getBuilderCredentials(config) { + const apiKey = + config?.polymarketBuilderApiKey ?? + config?.polymarketApiKey ?? + config?.polymarketClobApiKey; + const secret = + config?.polymarketBuilderSecret ?? + config?.polymarketApiSecret ?? + config?.polymarketClobApiSecret; + const passphrase = + config?.polymarketBuilderPassphrase ?? + config?.polymarketApiPassphrase ?? + config?.polymarketClobApiPassphrase; + return { apiKey, secret, passphrase }; +} + +function buildRelayerAuthHeaders({ + config, + method, + path, + bodyText, +}) { + const { apiKey, secret, passphrase } = getBuilderCredentials(config); + if (!apiKey || !secret || !passphrase) { + throw new Error( + 'Missing Polymarket builder credentials. Set POLYMARKET_BUILDER_API_KEY/POLYMARKET_BUILDER_SECRET/POLYMARKET_BUILDER_PASSPHRASE (or POLYMARKET_API_* / POLYMARKET_CLOB_* fallbacks).' + ); + } + + const timestamp = Date.now().toString(); + const payload = `${timestamp}${method.toUpperCase()}${path}${bodyText ?? ''}`; + const secretBytes = Buffer.from(secret, 'base64'); + const signature = crypto + .createHmac('sha256', secretBytes) + .update(payload) + .digest('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + return { + POLY_BUILDER_API_KEY: apiKey, + POLY_BUILDER_SIGNATURE: signature, + POLY_BUILDER_TIMESTAMP: timestamp, + POLY_BUILDER_PASSPHRASE: passphrase, + }; +} + +function buildPathWithQuery(basePath, params) { + const urlParams = new URLSearchParams(); + for (const [key, value] of Object.entries(params ?? {})) { + if (value === undefined || value === null || value === '') continue; + urlParams.set(key, String(value)); + } + const query = urlParams.toString(); + return query.length > 0 ? `${basePath}?${query}` : basePath; +} + +function uniqueList(values) { + return [...new Set(values.filter(Boolean))]; +} + +function buildSignerQueryCandidates(basePath, signerAddress, txType) { + const address = getAddress(signerAddress); + const normalizedType = normalizeRelayerTxType(txType); + const typeCandidates = uniqueList([ + normalizedType, + normalizedType.toLowerCase(), + ]); + + const paths = []; + for (const signerType of typeCandidates) { + paths.push(buildPathWithQuery(basePath, { address, type: signerType })); + paths.push(buildPathWithQuery(basePath, { address, signerType })); + paths.push(buildPathWithQuery(basePath, { signerAddress: address, signerType })); + paths.push(buildPathWithQuery(basePath, { signerAddress: address, type: signerType })); + } + return uniqueList(paths); +} + +async function sleep(ms) { + if (ms <= 0) return; + await new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function relayerRequest({ + config, + method, + path, + body, +}) { + const host = normalizeRelayerHost(config?.polymarketRelayerHost); + const bodyText = body === undefined ? '' : JSON.stringify(body); + const timeoutMs = normalizeNonNegativeInteger( + config?.polymarketRelayerRequestTimeoutMs, + DEFAULT_RELAYER_REQUEST_TIMEOUT_MS + ); + const headers = { + 'Content-Type': 'application/json', + ...buildRelayerAuthHeaders({ + config, + method, + path, + bodyText, + }), + }; + + const response = await fetch(`${host}${path}`, { + method, + headers, + body: body === undefined ? undefined : bodyText, + signal: AbortSignal.timeout(timeoutMs), + }); + const text = await response.text(); + let parsed; + try { + parsed = text ? JSON.parse(text) : null; + } catch (error) { + parsed = { raw: text }; + } + + if (!response.ok) { + const requestError = new Error( + `Relayer request failed (${method} ${path}): ${response.status} ${response.statusText} ${text}` + ); + requestError.statusCode = response.status; + requestError.responseBody = parsed; + throw requestError; + } + + return parsed; +} + +async function relayerRequestFirst({ + config, + method, + candidatePaths, + body, +}) { + let lastError; + + for (const path of candidatePaths) { + try { + const payload = await relayerRequest({ + config, + method, + path, + body, + }); + return { payload, path }; + } catch (error) { + lastError = error; + } + } + + throw lastError ?? new Error(`Relayer request failed for ${method} ${candidatePaths.join(', ')}`); +} + +function collectPayloadObjects(payload) { + const out = []; + const seen = new Set(); + const queue = [payload]; + + while (queue.length > 0 && out.length < 24) { + const current = queue.shift(); + if (!current || typeof current !== 'object') continue; + if (seen.has(current)) continue; + seen.add(current); + out.push(current); + + for (const value of Object.values(current)) { + if (value && typeof value === 'object') { + queue.push(value); + } + } + } + + return out; +} + +function extractStringField(payload, fieldNames) { + const candidates = collectPayloadObjects(payload); + for (const candidate of candidates) { + for (const fieldName of fieldNames) { + const value = candidate?.[fieldName]; + if (typeof value === 'string' && value.trim().length > 0) { + return value.trim(); + } + if (typeof value === 'number' && Number.isFinite(value)) { + return String(value); + } + if (typeof value === 'bigint') { + return value.toString(); + } + } + } + return null; +} + +function extractBooleanField(payload, fieldNames) { + const candidates = collectPayloadObjects(payload); + for (const candidate of candidates) { + for (const fieldName of fieldNames) { + const value = candidate?.[fieldName]; + if (typeof value === 'boolean') return value; + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase(); + if (normalized === 'true') return true; + if (normalized === 'false') return false; + } + if (typeof value === 'number') { + if (value === 1) return true; + if (value === 0) return false; + } + } + } + return null; +} + +function extractRelayerTxHash(payload) { + const hashCandidate = extractStringField(payload, [ + 'txHash', + 'relayTxHash', + 'relay_hash', + 'hash', + ]); + return normalizeHashOrNull(hashCandidate); +} + +function extractTransactionHash(payload) { + const hashCandidate = extractStringField(payload, [ + 'transactionHash', + 'transaction_hash', + 'chainTxHash', + 'minedTransactionHash', + ]); + return normalizeHashOrNull(hashCandidate); +} + +function extractTransactionId(payload) { + const idCandidate = extractStringField(payload, [ + 'transactionID', + 'transactionId', + 'id', + ]); + return idCandidate && idCandidate.length > 0 ? idCandidate : null; +} + +function normalizeRelayerState(value) { + if (typeof value !== 'string') return null; + const normalized = value.trim().toUpperCase(); + if (!normalized) return null; + if (!normalized.startsWith('STATE_')) { + if (normalized === 'PENDING') return 'STATE_PENDING'; + if (normalized === 'MINED') return 'STATE_MINED'; + if (normalized === 'CONFIRMED') return 'STATE_CONFIRMED'; + if (normalized === 'FAILED') return 'STATE_FAILED'; + if (normalized === 'INVALID') return 'STATE_INVALID'; + } + return normalized; +} + +function extractRelayerStatus(payload) { + const statusCandidate = extractStringField(payload, ['state', 'status', 'txStatus']); + if (!statusCandidate) return null; + return normalizeRelayerState(statusCandidate); +} + +function extractTransactionRecord(payload) { + if (Array.isArray(payload)) { + return payload.length > 0 ? payload[0] : null; + } + if (payload && typeof payload === 'object') { + if (Array.isArray(payload.transactions) && payload.transactions.length > 0) { + return payload.transactions[0]; + } + if (payload.transaction && typeof payload.transaction === 'object') { + return payload.transaction; + } + } + return payload; +} + +function getSafeFactoryAddress(config) { + const configured = normalizeAddressOrNull(config?.polymarketRelayerSafeFactory); + return configured ? getAddress(configured) : SAFE_FACTORY_ADDRESS; +} + +function getProxyFactoryAddress(config) { + const configured = normalizeAddressOrNull(config?.polymarketRelayerProxyFactory); + return configured ? getAddress(configured) : PROXY_FACTORY_ADDRESS; +} + +function deriveSafeAddress({ signerAddress, safeFactory }) { + return getCreate2Address({ + from: getAddress(safeFactory), + salt: keccak256( + encodeAbiParameters( + [ + { + type: 'address', + }, + ], + [getAddress(signerAddress)] + ) + ), + bytecodeHash: SAFE_INIT_CODE_HASH, + }); +} + +function deriveProxyAddress({ signerAddress, proxyFactory }) { + return getCreate2Address({ + from: getAddress(proxyFactory), + salt: keccak256(encodePacked(['address'], [getAddress(signerAddress)])), + bytecodeHash: PROXY_INIT_CODE_HASH, + }); +} + +function splitAndPackSignature(signature) { + if (typeof signature !== 'string' || !/^0x[0-9a-fA-F]{130}$/.test(signature)) { + throw new Error('Invalid 65-byte signature.'); + } + + const body = signature.slice(2); + const r = body.slice(0, 64); + const s = body.slice(64, 128); + const vRaw = Number.parseInt(body.slice(128, 130), 16); + + let safeV = vRaw; + if (safeV === 0 || safeV === 1) { + safeV += 31; + } else if (safeV === 27 || safeV === 28) { + safeV += 4; + } + + if (safeV < 31 || safeV > 32) { + throw new Error(`Unexpected signature v=${vRaw} while packing SAFE signature.`); + } + + return `0x${r}${s}${safeV.toString(16).padStart(2, '0')}`; +} + +function isPolymarketRelayerEnabled(config) { + return Boolean(config?.polymarketRelayerEnabled); +} + +async function getRelayerPayload({ + config, + signerAddress, + txType, +}) { + const payloadCandidates = buildSignerQueryCandidates( + RELAYER_ENDPOINTS.RELAY_PAYLOAD, + signerAddress, + txType + ); + + try { + const { payload } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: payloadCandidates, + }); + return payload; + } catch (error) { + return null; + } +} + +async function getRelayerProxyAddress({ + config, + signerAddress, + txType = RELAYER_TX_TYPE.SAFE, +}) { + const payload = await getRelayerPayload({ + config, + signerAddress, + txType, + }); + + let candidate = extractStringField(payload, [ + 'address', + 'proxyWallet', + 'proxyAddress', + 'walletAddress', + ]); + + if (!candidate) { + try { + const { payload: addressPayload } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: buildSignerQueryCandidates( + RELAYER_ENDPOINTS.ADDRESS, + signerAddress, + txType + ), + }); + candidate = extractStringField(addressPayload, [ + 'address', + 'proxyWallet', + 'proxyAddress', + 'walletAddress', + ]); + } catch (error) { + // Continue to legacy fallback. + } + } + + if (!candidate) { + try { + const { payload: legacyPayload } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: [LEGACY_ENDPOINTS.PROXY_ADDRESS(getAddress(signerAddress))], + }); + candidate = extractStringField(legacyPayload, [ + 'proxyWallet', + 'proxyAddress', + 'walletAddress', + 'address', + ]); + } catch (error) { + // No legacy proxy-address support. + } + } + + const normalized = normalizeAddressOrNull(candidate); + return normalized ? getAddress(normalized) : null; +} + +async function getRelayerNonce({ + config, + signerAddress, + txType, + proxyAddress, +}) { + const payload = await getRelayerPayload({ + config, + signerAddress, + txType, + }); + + let nonceCandidate = extractStringField(payload, ['nonce']); + + if (!nonceCandidate) { + try { + const { payload: noncePayload } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: buildSignerQueryCandidates( + RELAYER_ENDPOINTS.NONCE, + signerAddress, + txType + ), + }); + nonceCandidate = extractStringField(noncePayload, ['nonce']); + } catch (error) { + // Continue to legacy fallback. + } + } + + if (!nonceCandidate && proxyAddress) { + try { + const { payload: legacyNoncePayload } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: [LEGACY_ENDPOINTS.PROXY_NONCE(getAddress(proxyAddress))], + }); + nonceCandidate = extractStringField(legacyNoncePayload, ['nonce']); + } catch (error) { + // Continue. + } + } + + if (!nonceCandidate) { + throw new Error('Relayer nonce response did not include nonce.'); + } + + return BigInt(nonceCandidate); +} + +async function getSafeDeployed({ + config, + safeAddress, +}) { + try { + const { payload } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: uniqueList([ + buildPathWithQuery(RELAYER_ENDPOINTS.DEPLOYED, { + address: getAddress(safeAddress), + }), + buildPathWithQuery(RELAYER_ENDPOINTS.DEPLOYED, { + proxyWallet: getAddress(safeAddress), + }), + ]), + }); + return extractBooleanField(payload, ['deployed', 'isDeployed', 'safeDeployed']); + } catch (error) { + return null; + } +} + +async function buildSafeCreateRequest({ + walletClient, + account, + chainId, + signerAddress, + safeFactory, + safeAddress, + metadata, +}) { + if (!walletClient || typeof walletClient.signTypedData !== 'function') { + throw new Error( + 'Runtime signer does not support signTypedData; cannot create SAFE proxy wallet through relayer.' + ); + } + + const signature = await walletClient.signTypedData({ + account, + domain: { + name: CREATE_PROXY_DOMAIN_NAME, + chainId: Number(chainId), + verifyingContract: getAddress(safeFactory), + }, + types: CREATE_PROXY_EIP712_TYPES, + primaryType: 'CreateProxy', + message: { + paymentToken: zeroAddress, + payment: 0n, + paymentReceiver: zeroAddress, + }, + }); + + return { + from: getAddress(signerAddress), + to: getAddress(safeFactory), + proxyWallet: getAddress(safeAddress), + data: '0x', + signature, + signatureParams: { + paymentToken: zeroAddress, + payment: '0', + paymentReceiver: zeroAddress, + }, + type: 'SAFE-CREATE', + metadata: normalizeMetadata(metadata), + }; +} + +async function signSafeTransaction({ + walletClient, + account, + chainId, + signerAddress, + proxyWallet, + toAddress, + value, + data, + operation, + nonce, + metadata, +}) { + if (!walletClient || typeof walletClient.signMessage !== 'function') { + throw new Error( + 'Runtime signer does not support signMessage; cannot sign SAFE relayer transaction.' + ); + } + if (!Number.isInteger(operation) || operation < 0 || operation > 1) { + throw new Error('SAFE relayer transaction operation must be 0 or 1.'); + } + + const safeTxHash = hashTypedData({ + domain: { + chainId: Number(chainId), + verifyingContract: getAddress(proxyWallet), + }, + primaryType: 'SafeTx', + types: SAFE_EIP712_TYPES, + message: { + to: getAddress(toAddress), + value: BigInt(value), + data: normalizeHexData(data), + operation, + safeTxGas: 0n, + baseGas: 0n, + gasPrice: 0n, + gasToken: zeroAddress, + refundReceiver: zeroAddress, + nonce: BigInt(nonce), + }, + }); + + const signature = await walletClient.signMessage({ + account, + message: { raw: safeTxHash }, + }); + + return { + request: { + from: getAddress(signerAddress), + to: getAddress(toAddress), + proxyWallet: getAddress(proxyWallet), + data: normalizeHexData(data), + nonce: BigInt(nonce).toString(), + signature: splitAndPackSignature(signature), + signatureParams: { + gasPrice: '0', + operation: String(operation), + safeTxnGas: '0', + baseGas: '0', + gasToken: zeroAddress, + refundReceiver: zeroAddress, + }, + type: RELAYER_TX_TYPE.SAFE, + metadata: normalizeMetadata(metadata), + }, + txHash: safeTxHash, + }; +} + +async function signProxyTransaction({ + walletClient, + account, + chainId, + signerAddress, + proxyWallet, + toAddress, + data, + nonce, + metadata, +}) { + if (!walletClient || typeof walletClient.signMessage !== 'function') { + throw new Error( + 'Runtime signer does not support signMessage; cannot sign PROXY relayer transaction.' + ); + } + + const encoded = encodePacked( + ['uint256', 'address', 'address', 'bytes', 'uint256'], + [ + BigInt(chainId), + getAddress(proxyWallet), + getAddress(toAddress), + normalizeHexData(data), + BigInt(nonce), + ] + ); + + const proxyTxHash = keccak256(encoded); + const signature = await walletClient.signMessage({ + account, + message: { raw: proxyTxHash }, + }); + + return { + request: { + from: getAddress(signerAddress), + to: getAddress(toAddress), + proxyWallet: getAddress(proxyWallet), + data: normalizeHexData(data), + nonce: BigInt(nonce).toString(), + signature, + signatureParams: { + chainId: String(chainId), + }, + type: RELAYER_TX_TYPE.PROXY, + metadata: normalizeMetadata(metadata), + }, + txHash: proxyTxHash, + }; +} + +async function submitRelayerTransaction({ + config, + transactionRequest, +}) { + const { payload, path } = await relayerRequestFirst({ + config, + method: 'POST', + candidatePaths: [RELAYER_ENDPOINTS.SUBMIT, LEGACY_ENDPOINTS.TRANSACTION], + body: transactionRequest, + }); + + return { + payload, + path, + transactionId: extractTransactionId(payload), + relayTxHash: extractRelayerTxHash(payload), + transactionHash: extractTransactionHash(payload), + state: extractRelayerStatus(payload), + }; +} + +async function fetchTransactionStatus({ + config, + transactionId, + relayTxHash, +}) { + const candidatePaths = []; + + if (transactionId) { + candidatePaths.push( + buildPathWithQuery(RELAYER_ENDPOINTS.TRANSACTION, { id: transactionId }), + buildPathWithQuery(RELAYER_ENDPOINTS.TRANSACTION, { + transactionID: transactionId, + }), + buildPathWithQuery(RELAYER_ENDPOINTS.TRANSACTION, { + transactionId, + }) + ); + } + + if (relayTxHash) { + candidatePaths.push( + buildPathWithQuery(RELAYER_ENDPOINTS.TRANSACTION, { hash: relayTxHash }), + buildPathWithQuery(RELAYER_ENDPOINTS.TRANSACTION, { txHash: relayTxHash }), + LEGACY_ENDPOINTS.TRANSACTION_STATUS(relayTxHash) + ); + } + + const { payload, path } = await relayerRequestFirst({ + config, + method: 'GET', + candidatePaths: uniqueList(candidatePaths), + }); + + const record = extractTransactionRecord(payload); + const state = extractRelayerStatus(record ?? payload); + const nextTransactionId = extractTransactionId(record ?? payload) ?? transactionId; + const nextRelayTxHash = extractRelayerTxHash(record ?? payload) ?? relayTxHash; + const transactionHash = extractTransactionHash(record ?? payload); + + return { + payload, + path, + state, + transactionId: nextTransactionId, + relayTxHash: nextRelayTxHash, + transactionHash, + }; +} + +async function waitForRelayerTransaction({ + config, + transactionId, + relayTxHash, +}) { + if (!transactionId && !relayTxHash) { + throw new Error('waitForRelayerTransaction requires transactionId or relayTxHash.'); + } + + const pollIntervalMs = normalizeNonNegativeInteger( + config?.polymarketRelayerPollIntervalMs, + DEFAULT_RELAYER_POLL_INTERVAL_MS + ); + const timeoutMs = normalizeNonNegativeInteger( + config?.polymarketRelayerPollTimeoutMs, + DEFAULT_RELAYER_POLL_TIMEOUT_MS + ); + const deadline = Date.now() + timeoutMs; + let lastStatus; + let currentTransactionId = transactionId; + let currentRelayTxHash = relayTxHash; + + while (Date.now() <= deadline) { + const statusResult = await fetchTransactionStatus({ + config, + transactionId: currentTransactionId, + relayTxHash: currentRelayTxHash, + }); + + currentTransactionId = statusResult.transactionId ?? currentTransactionId; + currentRelayTxHash = statusResult.relayTxHash ?? currentRelayTxHash; + lastStatus = statusResult; + + if (statusResult.state && RELAYER_SUCCESS_STATES.has(statusResult.state)) { + return { + ...statusResult, + transactionId: currentTransactionId, + relayTxHash: currentRelayTxHash, + }; + } + + if (statusResult.state && RELAYER_FAILURE_STATES.has(statusResult.state)) { + throw new Error( + `Relayer transaction failed with state=${statusResult.state} for transactionId=${currentTransactionId ?? 'unknown'} relayTxHash=${currentRelayTxHash ?? 'unknown'}.` + ); + } + + await sleep(pollIntervalMs); + } + + throw new Error( + `Timed out waiting for relayer transaction. transactionId=${currentTransactionId ?? 'unknown'} relayTxHash=${currentRelayTxHash ?? 'unknown'} lastStatus=${JSON.stringify( + lastStatus?.payload ?? null + )}` + ); +} + +async function createProxyWallet({ + config, + signerAddress, + chainId, + txType, +}) { + return relayerRequest({ + config, + method: 'POST', + path: LEGACY_ENDPOINTS.CREATE_PROXY, + body: { + from: getAddress(signerAddress), + chainId: Number(chainId), + relayerTxType: txType, + }, + }); +} + +async function resolveProxyWalletAddress({ + config, + signerAddress, + chainId, + txType, + explicitProxyWallet, +}) { + if (explicitProxyWallet) { + return getAddress(explicitProxyWallet); + } + if (config?.polymarketRelayerFromAddress) { + return getAddress(config.polymarketRelayerFromAddress); + } + + const resolveProxyAddress = config?.polymarketRelayerResolveProxyAddress !== false; + if (resolveProxyAddress) { + try { + const existingProxy = await getRelayerProxyAddress({ + config, + signerAddress, + txType, + }); + if (existingProxy) { + return existingProxy; + } + } catch (error) { + // Continue to deterministic derivation and optional deployment. + } + } + + const derivedAddress = + txType === RELAYER_TX_TYPE.SAFE + ? deriveSafeAddress({ + signerAddress, + safeFactory: getSafeFactoryAddress(config), + }) + : deriveProxyAddress({ + signerAddress, + proxyFactory: getProxyFactoryAddress(config), + }); + + if (txType === RELAYER_TX_TYPE.PROXY && config?.polymarketRelayerAutoDeployProxy) { + const deployResponse = await createProxyWallet({ + config, + signerAddress, + chainId, + txType, + }); + const deployTxId = extractTransactionId(deployResponse); + const deployTxHash = extractRelayerTxHash(deployResponse); + await waitForRelayerTransaction({ + config, + transactionId: deployTxId, + relayTxHash: deployTxHash, + }); + } + + return derivedAddress; +} + +async function resolveRelayerProxyWallet({ + publicClient, + account, + config, + proxyWallet, +}) { + if (!isPolymarketRelayerEnabled(config)) { + throw new Error('Polymarket relayer is disabled (POLYMARKET_RELAYER_ENABLED=false).'); + } + if (!publicClient) { + throw new Error('publicClient is required to resolve relayer proxy wallet.'); + } + + const signerAddress = getAddress(account?.address); + const chainId = Number( + config?.polymarketRelayerChainId ?? + (typeof publicClient.getChainId === 'function' + ? await publicClient.getChainId() + : undefined) + ); + + if (!Number.isInteger(chainId) || chainId <= 0) { + throw new Error( + 'Unable to resolve chainId for relayer transaction. Set POLYMARKET_RELAYER_CHAIN_ID.' + ); + } + + const txType = normalizeRelayerTxType(config?.polymarketRelayerTxType); + const resolvedProxyWallet = await resolveProxyWalletAddress({ + config, + signerAddress, + chainId, + txType, + explicitProxyWallet: proxyWallet, + }); + + if (!resolvedProxyWallet) { + throw new Error( + 'Unable to resolve relayer proxy wallet address. Set POLYMARKET_RELAYER_FROM_ADDRESS, or enable relayer proxy resolution/auto-deploy.' + ); + } + + return { + signerAddress, + chainId, + txType, + proxyWallet: resolvedProxyWallet, + }; +} + +async function ensureSafeDeployed({ + config, + walletClient, + account, + chainId, + signerAddress, + safeAddress, +}) { + const deployed = await getSafeDeployed({ + config, + safeAddress, + }); + + if (deployed === true) { + return; + } + + if (deployed === null) { + console.warn( + `[agent] Unable to verify SAFE deployment for ${safeAddress} via relayer /deployed endpoint; proceeding without deployment check.` + ); + return; + } + + if (!config?.polymarketRelayerAutoDeployProxy) { + throw new Error( + `SAFE proxy wallet ${safeAddress} appears undeployed. Enable POLYMARKET_RELAYER_AUTO_DEPLOY_PROXY=true to deploy automatically or deploy it out of band.` + ); + } + + const createRequest = await buildSafeCreateRequest({ + walletClient, + account, + chainId, + signerAddress, + safeFactory: getSafeFactoryAddress(config), + safeAddress, + metadata: 'Relayer SAFE deployment', + }); + + const createSubmission = await submitRelayerTransaction({ + config, + transactionRequest: createRequest, + }); + + await waitForRelayerTransaction({ + config, + transactionId: createSubmission.transactionId, + relayTxHash: createSubmission.relayTxHash, + }); +} + +async function relayPolymarketTransaction({ + publicClient, + walletClient, + account, + config, + proxyWallet, + to, + data, + value = 0n, + operation = 0, + nonce, + metadata, +}) { + if (!isPolymarketRelayerEnabled(config)) { + throw new Error('Polymarket relayer is disabled (POLYMARKET_RELAYER_ENABLED=false).'); + } + if (!publicClient) { + throw new Error('publicClient is required for relayer transaction submission.'); + } + if (!walletClient) { + throw new Error('walletClient is required for relayer transaction submission.'); + } + + const resolved = await resolveRelayerProxyWallet({ + publicClient, + account, + config, + proxyWallet, + }); + const signerAddress = resolved.signerAddress; + const chainId = resolved.chainId; + const txType = resolved.txType; + const resolvedProxyWallet = resolved.proxyWallet; + + if (txType === RELAYER_TX_TYPE.SAFE) { + const expectedSafeAddress = deriveSafeAddress({ + signerAddress, + safeFactory: getSafeFactoryAddress(config), + }); + if (resolvedProxyWallet.toLowerCase() !== expectedSafeAddress.toLowerCase()) { + throw new Error( + `Configured SAFE proxy wallet ${resolvedProxyWallet} does not match expected relayer SAFE address ${expectedSafeAddress} for signer ${signerAddress}.` + ); + } + await ensureSafeDeployed({ + config, + walletClient, + account, + chainId, + signerAddress, + safeAddress: resolvedProxyWallet, + }); + } + + const normalizedTo = getAddress(to); + const normalizedData = normalizeHexData(data); + const normalizedValue = BigInt(value ?? 0n); + const normalizedOperation = Number(operation ?? 0); + + const normalizedNonce = + nonce === undefined || nonce === null + ? await getRelayerNonce({ + config, + signerAddress, + txType, + proxyAddress: resolvedProxyWallet, + }) + : BigInt(nonce); + + const signed = + txType === RELAYER_TX_TYPE.SAFE + ? await signSafeTransaction({ + walletClient, + account, + chainId, + signerAddress, + proxyWallet: resolvedProxyWallet, + toAddress: normalizedTo, + value: normalizedValue, + data: normalizedData, + operation: normalizedOperation, + nonce: normalizedNonce, + metadata, + }) + : await signProxyTransaction({ + walletClient, + account, + chainId, + signerAddress, + proxyWallet: resolvedProxyWallet, + toAddress: normalizedTo, + data: normalizedData, + nonce: normalizedNonce, + metadata, + }); + + const submission = await submitRelayerTransaction({ + config, + transactionRequest: signed.request, + }); + if (!submission.transactionId && !submission.relayTxHash) { + throw new Error( + 'Relayer submission did not return transactionID or txHash; cannot track transaction lifecycle.' + ); + } + + const waited = await waitForRelayerTransaction({ + config, + transactionId: submission.transactionId, + relayTxHash: submission.relayTxHash, + }); + + let transactionHash = waited.transactionHash ?? submission.transactionHash; + if (!transactionHash && waited.relayTxHash) { + try { + await publicClient.getTransactionReceipt({ hash: waited.relayTxHash }); + transactionHash = waited.relayTxHash; + } catch (error) { + // Relay tx hash is not always the chain transaction hash. + } + } + + if (!transactionHash) { + throw new Error( + `Relayer transaction reached state=${waited.state ?? 'unknown'} without transactionHash. transactionId=${waited.transactionId ?? 'unknown'} relayTxHash=${waited.relayTxHash ?? 'unknown'}` + ); + } + + return { + transactionHash, + relayTxHash: waited.relayTxHash ?? submission.relayTxHash ?? null, + transactionId: waited.transactionId ?? submission.transactionId ?? null, + state: waited.state ?? submission.state ?? null, + from: signerAddress, + proxyWallet: resolvedProxyWallet, + txType, + nonce: normalizedNonce.toString(), + submitResponse: submission.payload, + statusResponse: waited.payload, + }; +} + +export { + RELAYER_TX_TYPE, + getRelayerProxyAddress, + isPolymarketRelayerEnabled, + relayPolymarketTransaction, + resolveRelayerProxyWallet, +}; diff --git a/agent/src/lib/polymarket.js b/agent/src/lib/polymarket.js new file mode 100644 index 00000000..5566e7a7 --- /dev/null +++ b/agent/src/lib/polymarket.js @@ -0,0 +1,512 @@ +import crypto from 'node:crypto'; +import { getAddress, zeroAddress } from 'viem'; + +const DEFAULT_CLOB_HOST = 'https://clob.polymarket.com'; +const DEFAULT_CLOB_REQUEST_TIMEOUT_MS = 15_000; +const DEFAULT_CLOB_MAX_RETRIES = 1; +const DEFAULT_CLOB_RETRY_DELAY_MS = 250; +const DATA_API_HOST = 'https://data-api.polymarket.com'; +const DEFAULT_COLLATERAL_TOKEN = '0x2791bca1f2de4661ed88a30c99a7a9449aa84174'; +const CLOB_SUCCESS_TERMINAL_STATUS = 'CONFIRMED'; +const CLOB_FAILURE_TERMINAL_STATUS = 'FAILED'; +const CLOB_ORDER_FAILURE_STATUSES = new Set([ + 'FAILED', + 'REJECTED', + 'CANCELED', + 'CANCELLED', + 'EXPIRED', +]); +const CLOB_ORDER_FILLED_STATUSES = new Set(['FILLED', 'MATCHED', 'CONFIRMED']); +const CLOB_EIP712_DOMAIN_NAME = 'Polymarket CTF Exchange'; +const CLOB_EIP712_DOMAIN_VERSION = '1'; +const DEFAULT_EIP712_ORDER_SIDE = 0; +const DEFAULT_EIP712_SIGNATURE_TYPE = 0; +const ORDER_EIP712_TYPES = Object.freeze([ + { name: 'salt', type: 'uint256' }, + { name: 'maker', type: 'address' }, + { name: 'signer', type: 'address' }, + { name: 'taker', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'makerAmount', type: 'uint256' }, + { name: 'takerAmount', type: 'uint256' }, + { name: 'expiration', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'feeRateBps', type: 'uint256' }, + { name: 'side', type: 'uint8' }, + { name: 'signatureType', type: 'uint8' }, +]); +const SIDE_INDEX = Object.freeze({ + BUY: 0, + SELL: 1, +}); +const SIGNATURE_TYPE_INDEX = Object.freeze({ + EOA: 0, + POLY_PROXY: 1, + POLY_GNOSIS_SAFE: 2, +}); +const DEFAULT_CTF_EXCHANGE_BY_CHAIN_ID = Object.freeze({ + 137: '0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e', + 80002: '0xdfe02eb6733538f8ea35d585af8de5958ad99e40', +}); + +function normalizeNonNegativeInteger(value, fallback) { + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed < 0) return fallback; + return Math.floor(parsed); +} + +function normalizeClobHost(host) { + return (host ?? DEFAULT_CLOB_HOST).replace(/\/+$/, ''); +} + +function normalizeUint(value, fieldName, { allowZero = true } = {}) { + if (value === null || value === undefined || value === '') { + throw new Error(`${fieldName} is required.`); + } + + let normalized; + try { + normalized = BigInt(value); + } catch (error) { + throw new Error(`${fieldName} must be an integer value.`); + } + + if (normalized < 0n || (!allowZero && normalized === 0n)) { + throw new Error(`${fieldName} must be ${allowZero ? '>= 0' : '> 0'}.`); + } + + return normalized; +} + +function normalizeSideIndex(value) { + if (value === null || value === undefined || value === '') { + return DEFAULT_EIP712_ORDER_SIDE; + } + if (typeof value === 'number') { + if (value === 0 || value === 1) return value; + throw new Error('side must be BUY/SELL or enum index 0/1.'); + } + + if (typeof value === 'string') { + const normalized = value.trim().toUpperCase(); + if (normalized in SIDE_INDEX) { + return SIDE_INDEX[normalized]; + } + if (normalized === '0' || normalized === '1') { + return Number(normalized); + } + } + + throw new Error('side must be BUY/SELL or enum index 0/1.'); +} + +function normalizeSignatureTypeIndex(value) { + if (value === null || value === undefined || value === '') { + return DEFAULT_EIP712_SIGNATURE_TYPE; + } + if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 2) { + return value; + } + + if (typeof value === 'string') { + const normalized = value.trim().toUpperCase(); + if (normalized in SIGNATURE_TYPE_INDEX) { + return SIGNATURE_TYPE_INDEX[normalized]; + } + if (normalized === '0' || normalized === '1' || normalized === '2') { + return Number(normalized); + } + } + + throw new Error('signatureType must be EOA/POLY_GNOSIS_SAFE/POLY_PROXY or enum index 0/1/2.'); +} + +function randomSalt() { + return BigInt(`0x${crypto.randomBytes(32).toString('hex')}`).toString(); +} + +function resolveClobExchangeAddress({ chainId, exchangeOverride }) { + if (exchangeOverride) { + return getAddress(exchangeOverride); + } + + const exchange = DEFAULT_CTF_EXCHANGE_BY_CHAIN_ID[Number(chainId)]; + if (!exchange) { + throw new Error( + `No default Polymarket exchange for chainId=${chainId}. Set POLYMARKET_EXCHANGE or provide exchange in tool args.` + ); + } + return getAddress(exchange); +} + +function buildClobOrderFromRaw({ + maker, + signer, + taker, + tokenId, + makerAmount, + takerAmount, + expiration, + nonce, + feeRateBps, + side, + signatureType, + salt, +}) { + const normalizedMaker = getAddress(maker); + const normalizedSigner = getAddress(signer); + const normalizedTaker = taker ? getAddress(taker) : zeroAddress; + const normalizedTokenId = normalizeUint(tokenId, 'tokenId'); + const normalizedMakerAmount = normalizeUint(makerAmount, 'makerAmount', { allowZero: false }); + const normalizedTakerAmount = normalizeUint(takerAmount, 'takerAmount', { allowZero: false }); + const normalizedExpiration = normalizeUint(expiration ?? 0, 'expiration'); + const normalizedNonce = normalizeUint(nonce ?? 0, 'nonce'); + const normalizedFeeRateBps = normalizeUint(feeRateBps ?? 0, 'feeRateBps'); + const normalizedSide = normalizeSideIndex(side); + const normalizedSignatureType = normalizeSignatureTypeIndex(signatureType); + const normalizedSalt = normalizeUint(salt ?? randomSalt(), 'salt', { allowZero: false }); + + return { + salt: normalizedSalt.toString(), + maker: normalizedMaker, + signer: normalizedSigner, + taker: normalizedTaker, + tokenId: normalizedTokenId.toString(), + makerAmount: normalizedMakerAmount.toString(), + takerAmount: normalizedTakerAmount.toString(), + expiration: normalizedExpiration.toString(), + nonce: normalizedNonce.toString(), + feeRateBps: normalizedFeeRateBps.toString(), + side: normalizedSide, + signatureType: normalizedSignatureType, + }; +} + +async function signClobOrder({ + walletClient, + account, + chainId, + exchange, + order, + domainName = CLOB_EIP712_DOMAIN_NAME, + domainVersion = CLOB_EIP712_DOMAIN_VERSION, +}) { + if (!walletClient || typeof walletClient.signTypedData !== 'function') { + throw new Error( + 'Runtime signer does not support signTypedData; cannot build and sign CLOB orders.' + ); + } + + const normalizedChainId = Number(chainId); + if (!Number.isInteger(normalizedChainId) || normalizedChainId <= 0) { + throw new Error(`Invalid chainId for CLOB signing: ${chainId}`); + } + + const normalizedExchange = getAddress(exchange); + const normalizedOrder = buildClobOrderFromRaw(order); + const message = { + ...normalizedOrder, + salt: BigInt(normalizedOrder.salt), + tokenId: BigInt(normalizedOrder.tokenId), + makerAmount: BigInt(normalizedOrder.makerAmount), + takerAmount: BigInt(normalizedOrder.takerAmount), + expiration: BigInt(normalizedOrder.expiration), + nonce: BigInt(normalizedOrder.nonce), + feeRateBps: BigInt(normalizedOrder.feeRateBps), + side: Number(normalizedOrder.side), + signatureType: Number(normalizedOrder.signatureType), + }; + + const signature = await walletClient.signTypedData({ + account, + domain: { + name: domainName, + version: domainVersion, + chainId: normalizedChainId, + verifyingContract: normalizedExchange, + }, + types: { + Order: ORDER_EIP712_TYPES, + }, + primaryType: 'Order', + message, + }); + + return { + ...normalizedOrder, + signature, + }; +} + +function shouldRetryResponseStatus(status) { + return status === 429 || status >= 500; +} + +function shouldRetryError(error) { + if (!error) return false; + if (error.name === 'AbortError' || error.name === 'TimeoutError') return true; + if (error.name === 'TypeError') return true; + return false; +} + +function canRetryRequest({ method, path }) { + const normalizedMethod = method.toUpperCase(); + if (normalizedMethod === 'POST' && path === '/order') { + return false; + } + return true; +} + +async function sleep(ms) { + if (ms <= 0) return; + await new Promise((resolve) => setTimeout(resolve, ms)); +} + +function buildClobAuthHeaders({ + config, + signingAddress, + timestamp, + method, + path, + bodyText, +}) { + const apiKey = config.polymarketClobApiKey; + const apiSecret = config.polymarketClobApiSecret; + const apiPassphrase = config.polymarketClobApiPassphrase; + if (!signingAddress) { + throw new Error('Missing signingAddress for CLOB auth headers.'); + } + if (!apiKey || !apiSecret || !apiPassphrase) { + throw new Error( + 'Missing CLOB credentials. Set POLYMARKET_CLOB_API_KEY, POLYMARKET_CLOB_API_SECRET, and POLYMARKET_CLOB_API_PASSPHRASE.' + ); + } + + const payload = `${timestamp}${method.toUpperCase()}${path}${bodyText ?? ''}`; + const secretBytes = Buffer.from(apiSecret, 'base64'); + const signature = crypto + .createHmac('sha256', secretBytes) + .update(payload) + .digest('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + return { + 'POLY_ADDRESS': signingAddress, + 'POLY_API_KEY': apiKey, + 'POLY_SIGNATURE': signature, + 'POLY_TIMESTAMP': String(timestamp), + 'POLY_PASSPHRASE': apiPassphrase, + }; +} + +async function clobRequest({ + config, + signingAddress, + path, + method, + body, +}) { + const host = normalizeClobHost(config.polymarketClobHost); + const bodyText = body === undefined ? '' : JSON.stringify(body); + const timeoutMs = normalizeNonNegativeInteger( + config.polymarketClobRequestTimeoutMs, + DEFAULT_CLOB_REQUEST_TIMEOUT_MS + ); + const maxRetries = normalizeNonNegativeInteger( + config.polymarketClobMaxRetries, + DEFAULT_CLOB_MAX_RETRIES + ); + const retriesAllowed = canRetryRequest({ method, path }) ? maxRetries : 0; + const retryDelayMs = normalizeNonNegativeInteger( + config.polymarketClobRetryDelayMs, + DEFAULT_CLOB_RETRY_DELAY_MS + ); + + for (let attempt = 0; attempt <= retriesAllowed; attempt += 1) { + const timestamp = Math.floor(Date.now() / 1000); + const headers = { + 'Content-Type': 'application/json', + ...buildClobAuthHeaders({ + config, + signingAddress, + timestamp, + method, + path, + bodyText, + }), + }; + + try { + const response = await fetch(`${host}${path}`, { + method, + headers, + body: body === undefined ? undefined : bodyText, + signal: AbortSignal.timeout(timeoutMs), + }); + const text = await response.text(); + let parsed; + try { + parsed = text ? JSON.parse(text) : null; + } catch (error) { + parsed = { raw: text }; + } + + if (!response.ok) { + if (attempt < retriesAllowed && shouldRetryResponseStatus(response.status)) { + await sleep(retryDelayMs); + continue; + } + throw new Error( + `CLOB request failed (${method} ${path}): ${response.status} ${response.statusText} ${text}` + ); + } + + return parsed; + } catch (error) { + if (attempt < retriesAllowed && shouldRetryError(error)) { + await sleep(retryDelayMs); + continue; + } + throw error; + } + } + + throw new Error(`CLOB request failed (${method} ${path}) after retries.`); +} + +async function placeClobOrder({ + config, + signingAddress, + signedOrder, + ownerApiKey, + orderType, +}) { + if (!signedOrder || typeof signedOrder !== 'object') { + throw new Error('signedOrder is required and must be an object.'); + } + if (!ownerApiKey) { + throw new Error('ownerApiKey is required.'); + } + if (!orderType) { + throw new Error('orderType is required.'); + } + + const normalizedOrder = + signedOrder.order && typeof signedOrder.order === 'object' + ? signedOrder.order + : signedOrder; + + return clobRequest({ + config, + signingAddress, + method: 'POST', + path: '/order', + body: { + order: normalizedOrder, + owner: ownerApiKey, + orderType, + }, + }); +} + +async function getClobOrder({ config, signingAddress, orderId }) { + if (!orderId || typeof orderId !== 'string' || orderId.trim().length === 0) { + throw new Error('orderId is required.'); + } + + return clobRequest({ + config, + signingAddress, + method: 'GET', + path: `/data/order/${encodeURIComponent(orderId.trim())}`, + }); +} + +async function getClobTrades({ + config, + signingAddress, + maker, + taker, + market, + after, +}) { + if (!maker && !taker) { + throw new Error('getClobTrades requires maker or taker.'); + } + + const params = new URLSearchParams(); + if (maker) params.set('maker', String(maker)); + if (taker) params.set('taker', String(taker)); + if (market) params.set('market', String(market)); + if (after !== undefined && after !== null) params.set('after', String(after)); + + return clobRequest({ + config, + signingAddress, + method: 'GET', + path: `/data/trades?${params.toString()}`, + }); +} + +async function cancelClobOrders({ + config, + signingAddress, + mode, + orderIds, + market, + assetId, +}) { + if (mode === 'all') { + return clobRequest({ + config, + signingAddress, + method: 'DELETE', + path: '/cancel-all', + }); + } + + if (mode === 'market') { + if (!market && !assetId) { + throw new Error('cancel mode=market requires market or assetId.'); + } + return clobRequest({ + config, + signingAddress, + method: 'DELETE', + path: '/cancel-market-orders', + body: { + market, + asset_id: assetId, + }, + }); + } + + if (!Array.isArray(orderIds) || orderIds.length === 0) { + throw new Error('cancel mode=ids requires non-empty orderIds.'); + } + + return clobRequest({ + config, + signingAddress, + method: 'DELETE', + path: '/orders', + body: orderIds, + }); +} + +export { + CLOB_FAILURE_TERMINAL_STATUS, + CLOB_ORDER_FAILURE_STATUSES, + CLOB_ORDER_FILLED_STATUSES, + CLOB_SUCCESS_TERMINAL_STATUS, + DATA_API_HOST, + DEFAULT_COLLATERAL_TOKEN, + buildClobOrderFromRaw, + cancelClobOrders, + getClobOrder, + getClobTrades, + placeClobOrder, + resolveClobExchangeAddress, + signClobOrder, +}; diff --git a/agent/src/lib/price.js b/agent/src/lib/price.js new file mode 100644 index 00000000..6bc7d5e2 --- /dev/null +++ b/agent/src/lib/price.js @@ -0,0 +1,43 @@ +import { parseAbi } from 'viem'; + +const chainlinkAbi = parseAbi([ + 'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', +]); + +async function getEthPriceUSD(publicClient, priceFeedAddress = '0x694AA1769357215DE4FAC081bf1f309aDC325306') { + try { + const result = await publicClient.readContract({ + address: priceFeedAddress, + abi: chainlinkAbi, + functionName: 'latestRoundData', + }); + + const answer = result[1]; + const price = Number(answer) / 1e8; + console.log(`[price] ETH/USD from Chainlink: $${price.toFixed(2)}`); + return price; + } catch (error) { + console.error('[price] Failed to fetch ETH price from Chainlink:', error); + throw new Error('Unable to fetch ETH price from Chainlink oracle'); + } +} + +async function getEthPriceUSDFallback() { + try { + const response = await fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd' + ); + if (!response.ok) { + throw new Error(`Coingecko API error: ${response.status}`); + } + const data = await response.json(); + const price = data.ethereum.usd; + console.log(`[price] ETH/USD from Coingecko: $${price.toFixed(2)}`); + return price; + } catch (error) { + console.error('[price] Failed to fetch ETH price from Coingecko:', error); + throw error; + } +} + +export { getEthPriceUSD, getEthPriceUSDFallback }; diff --git a/agent/src/lib/signer.js b/agent/src/lib/signer.js new file mode 100644 index 00000000..bfe47593 --- /dev/null +++ b/agent/src/lib/signer.js @@ -0,0 +1,129 @@ +import { readFile } from 'node:fs/promises'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import { Wallet } from 'ethers'; +import { createWalletClient, getAddress, http } from 'viem'; +import { privateKeyToAccount, toAccount } from 'viem/accounts'; +import { mustGetEnv, normalizePrivateKey } from './utils.js'; + +const execFileAsync = promisify(execFile); + +async function loadPrivateKeyFromKeystore() { + const keystorePath = mustGetEnv('KEYSTORE_PATH'); + const keystorePassword = mustGetEnv('KEYSTORE_PASSWORD'); + const keystoreJson = await readFile(keystorePath, 'utf8'); + const wallet = await Wallet.fromEncryptedJson(keystoreJson, keystorePassword); + return wallet.privateKey; +} + +async function loadPrivateKeyFromKeychain() { + const service = mustGetEnv('KEYCHAIN_SERVICE'); + const account = mustGetEnv('KEYCHAIN_ACCOUNT'); + + if (process.platform === 'darwin') { + const { stdout } = await execFileAsync('security', [ + 'find-generic-password', + '-s', + service, + '-a', + account, + '-w', + ]); + return stdout.trim(); + } + + if (process.platform === 'linux') { + const { stdout } = await execFileAsync('secret-tool', [ + 'lookup', + 'service', + service, + 'account', + account, + ]); + return stdout.trim(); + } + + throw new Error('Keychain lookup not supported on this platform.'); +} + +async function loadPrivateKeyFromVault() { + const vaultAddr = mustGetEnv('VAULT_ADDR').replace(/\/+$/, ''); + const vaultToken = mustGetEnv('VAULT_TOKEN'); + const vaultPath = mustGetEnv('VAULT_SECRET_PATH').replace(/^\/+/, ''); + const vaultNamespace = process.env.VAULT_NAMESPACE; + const vaultKeyField = process.env.VAULT_SECRET_KEY ?? 'private_key'; + + const response = await fetch(`${vaultAddr}/v1/${vaultPath}`, { + headers: { + 'X-Vault-Token': vaultToken, + ...(vaultNamespace ? { 'X-Vault-Namespace': vaultNamespace } : {}), + }, + }); + + if (!response.ok) { + throw new Error(`Vault request failed (${response.status}).`); + } + + const payload = await response.json(); + const data = payload?.data?.data ?? payload?.data ?? {}; + const value = data[vaultKeyField]; + if (!value) { + throw new Error(`Vault secret missing key '${vaultKeyField}'.`); + } + + return value; +} + +async function createSignerClient({ rpcUrl }) { + const signerType = (process.env.SIGNER_TYPE ?? 'env').toLowerCase(); + + if (signerType === 'env') { + const privateKey = normalizePrivateKey(mustGetEnv('PRIVATE_KEY')); + const account = privateKeyToAccount(privateKey); + return { + account, + walletClient: createWalletClient({ account, transport: http(rpcUrl) }), + }; + } + + if (signerType === 'keystore') { + const privateKey = normalizePrivateKey(await loadPrivateKeyFromKeystore()); + const account = privateKeyToAccount(privateKey); + return { + account, + walletClient: createWalletClient({ account, transport: http(rpcUrl) }), + }; + } + + if (signerType === 'keychain') { + const privateKey = normalizePrivateKey(await loadPrivateKeyFromKeychain()); + const account = privateKeyToAccount(privateKey); + return { + account, + walletClient: createWalletClient({ account, transport: http(rpcUrl) }), + }; + } + + if (signerType === 'vault') { + const privateKey = normalizePrivateKey(await loadPrivateKeyFromVault()); + const account = privateKeyToAccount(privateKey); + return { + account, + walletClient: createWalletClient({ account, transport: http(rpcUrl) }), + }; + } + + if (['kms', 'vault-signer', 'signer-rpc', 'rpc', 'json-rpc'].includes(signerType)) { + const signerRpcUrl = mustGetEnv('SIGNER_RPC_URL'); + const signerAddress = getAddress(mustGetEnv('SIGNER_ADDRESS')); + const account = toAccount(signerAddress); + return { + account, + walletClient: createWalletClient({ account, transport: http(signerRpcUrl) }), + }; + } + + throw new Error(`Unsupported SIGNER_TYPE '${signerType}'.`); +} + +export { createSignerClient }; diff --git a/agent/src/lib/timelock.js b/agent/src/lib/timelock.js new file mode 100644 index 00000000..18bde130 --- /dev/null +++ b/agent/src/lib/timelock.js @@ -0,0 +1,148 @@ +const MONTHS_REGEX = + '(January|February|March|April|May|June|July|August|September|October|November|December)'; + +const NUMBER_WORDS = { + one: 1, + two: 2, + three: 3, + four: 4, + five: 5, + six: 6, + seven: 7, + eight: 8, + nine: 9, + ten: 10, + eleven: 11, + twelve: 12, +}; + +function parseNumber(value) { + if (!value) return null; + const trimmed = value.trim().toLowerCase(); + if (/^\d+$/.test(trimmed)) { + return Number(trimmed); + } + return NUMBER_WORDS[trimmed] ?? null; +} + +function unitToMs(unit) { + switch (unit.toLowerCase()) { + case 'minute': + case 'minutes': + return 60_000; + case 'hour': + case 'hours': + return 3_600_000; + case 'day': + case 'days': + return 86_400_000; + default: + return null; + } +} + +function extractAbsoluteTimelocks(rulesText) { + if (!rulesText) return []; + const regex = new RegExp( + `(after|on or after)\\s+${MONTHS_REGEX}\\s+\\d{1,2},\\s+\\d{4}[^.\\n]*`, + 'gi' + ); + const matches = []; + let match; + while ((match = regex.exec(rulesText)) !== null) { + const phrase = match[0]; + const raw = phrase.replace(/^(after|on or after)\s+/i, '').trim(); + const trimmed = raw.replace(/[\s.]+$/g, ''); + const hasTimezone = /\b(PST|PDT|UTC|GMT|Z)\b/i.test(trimmed); + let parsed = Date.parse(trimmed); + if (!Number.isFinite(parsed)) { + const cleaned = trimmed.replace(/\s+(PST|PDT|UTC|GMT)\b/i, ''); + parsed = Date.parse(cleaned); + } + if (!Number.isFinite(parsed)) { + const dateOnlyMatch = cleanedDateOnly(trimmed); + if (dateOnlyMatch) { + parsed = Date.parse(dateOnlyMatch); + } + } + if (!hasTimezone && Number.isFinite(parsed)) { + const hasTime = /\b\d{1,2}:\d{2}(\s*[AP]M)?\b/i.test(trimmed); + const dateOnlyMatch = hasTime ? null : cleanedDateOnly(trimmed); + const withUtc = dateOnlyMatch + ? `${dateOnlyMatch} 00:00 UTC` + : `${trimmed} UTC`; + const utcParsed = Date.parse(withUtc); + if (Number.isFinite(utcParsed)) { + parsed = utcParsed; + } + } + if (Number.isFinite(parsed)) { + matches.push({ + kind: 'absolute', + timestampMs: parsed, + source: phrase, + }); + } + } + return matches; +} + +function cleanedDateOnly(text) { + const dateOnly = new RegExp(`${MONTHS_REGEX}\\s+\\d{1,2},\\s+\\d{4}`, 'i'); + const match = text.match(dateOnly); + return match ? match[0] : null; +} + +function extractRelativeTimelocks(rulesText) { + if (!rulesText) return []; + const regex = /(\d+|\w+)\s*(minute|minutes|hour|hours|day|days)\s+after\s+deposit/gi; + const matches = []; + let match; + while ((match = regex.exec(rulesText)) !== null) { + const amount = parseNumber(match[1]); + const unitMs = unitToMs(match[2]); + if (!amount || !unitMs) continue; + matches.push({ + kind: 'relative', + offsetMs: amount * unitMs, + anchor: 'deposit', + source: match[0], + }); + } + return matches; +} + +function extractTimelockTriggers({ rulesText, deposits }) { + const triggers = []; + const absolute = extractAbsoluteTimelocks(rulesText); + for (const lock of absolute) { + triggers.push({ + id: `absolute:${lock.timestampMs}`, + kind: 'absolute', + timestampMs: lock.timestampMs, + source: lock.source, + }); + } + + const relative = extractRelativeTimelocks(rulesText); + if (relative.length > 0 && Array.isArray(deposits)) { + for (const deposit of deposits) { + if (!deposit?.timestampMs) continue; + for (const rule of relative) { + const ts = deposit.timestampMs + rule.offsetMs; + triggers.push({ + id: `relative:${deposit.id ?? deposit.transactionHash ?? deposit.blockNumber}:${rule.offsetMs}`, + kind: 'relative', + timestampMs: ts, + source: rule.source, + anchor: 'deposit', + deposit, + }); + } + } + } + + return triggers; +} + +export { extractTimelockTriggers }; diff --git a/agent/src/lib/tools.js b/agent/src/lib/tools.js new file mode 100644 index 00000000..83365df0 --- /dev/null +++ b/agent/src/lib/tools.js @@ -0,0 +1,1131 @@ +import { getAddress } from 'viem'; +import { + buildOgTransactions, + makeDeposit, + makeErc1155Deposit, + postBondAndDispute, + postBondAndPropose, +} from './tx.js'; +import { + buildClobOrderFromRaw, + cancelClobOrders, + placeClobOrder, + resolveClobExchangeAddress, + signClobOrder, +} from './polymarket.js'; +import { parseToolArguments } from './utils.js'; + +function safeStringify(value) { + return JSON.stringify(value, (_, item) => (typeof item === 'bigint' ? item.toString() : item)); +} + +function normalizeOrderSide(value) { + if (typeof value !== 'string') return undefined; + const normalized = value.trim().toUpperCase(); + return normalized === 'BUY' || normalized === 'SELL' ? normalized : undefined; +} + +function normalizeOrderSideEnumIndex(value) { + const normalized = normalizeOrderSide(value); + if (!normalized) return undefined; + return normalized === 'BUY' ? 0 : 1; +} + +function normalizeOrderType(value) { + if (typeof value !== 'string') return undefined; + const normalized = value.trim().toUpperCase(); + return normalized === 'GTC' || + normalized === 'GTD' || + normalized === 'FOK' || + normalized === 'FAK' + ? normalized + : undefined; +} + +function normalizeCancelMode(value) { + if (typeof value !== 'string') return undefined; + const normalized = value.trim().toLowerCase(); + return normalized === 'ids' || normalized === 'market' || normalized === 'all' + ? normalized + : undefined; +} + +function getFirstString(values) { + for (const value of values) { + if (typeof value === 'string' && value.trim()) { + return value.trim(); + } + } + return undefined; +} + +function maybeAddress(value) { + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + if (!/^0x[0-9a-fA-F]{40}$/.test(trimmed)) return undefined; + try { + return getAddress(trimmed); + } catch (error) { + return undefined; + } +} + +function normalizeOptionalUintString(value, fieldName) { + if (value === undefined || value === null || value === '') { + return undefined; + } + try { + const normalized = BigInt(value); + if (normalized < 0n) { + throw new Error(`${fieldName} must be >= 0.`); + } + return normalized.toString(); + } catch (error) { + throw new Error(`${fieldName} must be an integer value.`); + } +} + +function normalizeSignedOrderPayload(signedOrder) { + if (!signedOrder || typeof signedOrder !== 'object') { + return undefined; + } + return signedOrder.order && typeof signedOrder.order === 'object' + ? signedOrder.order + : signedOrder; +} + +function extractSignedOrderSideAndTokenId(orderPayload) { + if (!orderPayload || typeof orderPayload !== 'object') { + return { side: undefined, tokenId: undefined }; + } + + const side = normalizeOrderSide(orderPayload.side); + const tokenId = getFirstString([ + orderPayload.tokenId, + orderPayload.tokenID, + orderPayload.token_id, + orderPayload.assetId, + orderPayload.assetID, + orderPayload.asset_id, + ]); + + return { side, tokenId }; +} + +function extractSignedOrderIdentityAddresses(orderPayload) { + if (!orderPayload || typeof orderPayload !== 'object') { + return []; + } + + const candidates = [ + orderPayload.signer, + orderPayload.signerAddress, + orderPayload.maker, + orderPayload.makerAddress, + orderPayload.funder, + orderPayload.funderAddress, + orderPayload.user, + orderPayload.userAddress, + ]; + + const normalized = candidates.map(maybeAddress).filter(Boolean); + return Array.from(new Set(normalized)); +} + +function toolDefinitions({ + proposeEnabled, + disputeEnabled, + clobEnabled, + onchainToolsEnabled = proposeEnabled || disputeEnabled, +}) { + const tools = [ + { + type: 'function', + name: 'build_og_transactions', + description: + 'Build Optimistic Governor transaction payloads from high-level intents. Returns array of {to,value,data,operation} with value as string wei.', + strict: false, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + actions: { + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + kind: { + type: 'string', + description: + 'Action type: erc20_transfer | native_transfer | contract_call | uniswap_v3_exact_input_single | ctf_split | ctf_merge | ctf_redeem', + }, + token: { + type: ['string', 'null'], + description: + 'ERC20 token address for erc20_transfer.', + }, + to: { + type: ['string', 'null'], + description: 'Recipient or target contract address.', + }, + amountWei: { + type: ['string', 'null'], + description: + 'Amount in wei as a string. For erc20_transfer and native_transfer.', + }, + valueWei: { + type: ['string', 'null'], + description: + 'ETH value to send in contract_call (default 0).', + }, + abi: { + type: ['string', 'null'], + description: + 'Function signature for contract_call, e.g. "setOwner(address)".', + }, + args: { + type: ['array', 'null'], + description: + 'Arguments for contract_call in order, JSON-serializable.', + items: { + anyOf: [ + { type: 'string' }, + { type: 'number' }, + { type: 'boolean' }, + { type: 'null' }, + { + type: 'array', + items: { + anyOf: [ + { type: 'string' }, + { type: 'number' }, + { type: 'boolean' }, + { type: 'null' }, + ], + }, + }, + ], + }, + }, + operation: { + type: ['integer', 'null'], + description: + 'Safe operation (0=CALL,1=DELEGATECALL). Defaults to 0.', + }, + ctfContract: { + type: ['string', 'null'], + description: + 'ConditionalTokens contract address override for CTF actions.', + }, + collateralToken: { + type: ['string', 'null'], + description: 'Collateral token address for CTF actions.', + }, + conditionId: { + type: ['string', 'null'], + description: 'Condition id bytes32 for CTF actions.', + }, + parentCollectionId: { + type: ['string', 'null'], + description: + 'Parent collection id bytes32 for CTF actions (default zero bytes32).', + }, + partition: { + type: ['array', 'null'], + description: + 'Index partition for ctf_split/ctf_merge. Defaults to [1,2].', + items: { type: 'integer' }, + }, + amount: { + type: ['string', 'null'], + description: + 'Collateral/full-set amount in base units for ctf_split/ctf_merge.', + }, + indexSets: { + type: ['array', 'null'], + description: + 'Index sets for ctf_redeem. Defaults to [1,2].', + items: { type: 'integer' }, + }, + router: { + type: ['string', 'null'], + description: + 'Uniswap V3 router address for uniswap_v3_exact_input_single.', + }, + tokenIn: { + type: ['string', 'null'], + description: 'Input ERC20 token for Uniswap swap action.', + }, + tokenOut: { + type: ['string', 'null'], + description: 'Output ERC20 token for Uniswap swap action.', + }, + fee: { + type: ['integer', 'null'], + description: 'Uniswap V3 pool fee tier (e.g. 500, 3000, 10000).', + }, + recipient: { + type: ['string', 'null'], + description: 'Recipient of Uniswap swap output tokens.', + }, + amountInWei: { + type: ['string', 'null'], + description: 'Input token amount for Uniswap swap in token wei.', + }, + amountOutMinWei: { + type: ['string', 'null'], + description: 'Minimum output amount for Uniswap swap in token wei.', + }, + sqrtPriceLimitX96: { + type: ['string', 'null'], + description: + 'Optional Uniswap sqrtPriceLimitX96 guard (default 0 for no limit).', + }, + }, + required: ['kind'], + }, + }, + }, + required: ['actions'], + }, + }, + { + type: 'function', + name: 'make_deposit', + description: + 'Deposit funds into the commitment Safe. Use asset=0x000...000 for native ETH. amountWei must be a string of the integer wei amount.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + asset: { + type: 'string', + description: + 'Asset address (ERC20) or 0x0000000000000000000000000000000000000000 for native.', + }, + amountWei: { + type: 'string', + description: 'Amount in wei as a string.', + }, + }, + required: ['asset', 'amountWei'], + }, + }, + { + type: 'function', + name: 'make_erc1155_deposit', + description: + 'Deposit ERC1155 tokens into the commitment Safe using safeTransferFrom from the agent wallet.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + token: { + type: 'string', + description: 'ERC1155 token contract address.', + }, + tokenId: { + type: 'string', + description: 'ERC1155 token id as a base-10 string.', + }, + amount: { + type: 'string', + description: 'ERC1155 amount as a base-10 string.', + }, + data: { + type: ['string', 'null'], + description: 'Optional calldata bytes for safeTransferFrom, defaults to 0x.', + }, + }, + required: ['token', 'tokenId', 'amount'], + }, + }, + ]; + + if (!onchainToolsEnabled) { + tools.length = 0; + } + + if (onchainToolsEnabled && proposeEnabled) { + tools.push({ + type: 'function', + name: 'post_bond_and_propose', + description: + 'Post bond (if required) and propose transactions to the Optimistic Governor.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + transactions: { + type: 'array', + description: 'Safe transaction batch to propose. Use value as string wei.', + items: { + type: 'object', + additionalProperties: false, + properties: { + to: { type: 'string' }, + value: { type: 'string' }, + data: { type: 'string' }, + operation: { type: 'integer' }, + }, + required: ['to', 'value', 'data', 'operation'], + }, + }, + }, + required: ['transactions'], + }, + }); + } + + if (onchainToolsEnabled && disputeEnabled) { + tools.push({ + type: 'function', + name: 'dispute_assertion', + description: + 'Post bond (if required) and dispute an assertion on the Optimistic Oracle. Provide a short human-readable explanation.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + assertionId: { + type: 'string', + description: 'Assertion ID to dispute.', + }, + explanation: { + type: 'string', + description: 'Short human-readable dispute rationale.', + }, + }, + required: ['assertionId', 'explanation'], + }, + }); + } + + if (clobEnabled) { + tools.push( + { + type: 'function', + name: 'polymarket_clob_place_order', + description: + 'Submit a signed Polymarket CLOB order (BUY or SELL) to the CLOB API.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + owner: { + type: ['string', 'null'], + description: + 'Optional CLOB API key owner override; defaults to POLYMARKET_CLOB_API_KEY.', + }, + side: { + type: 'string', + description: 'BUY or SELL.', + }, + tokenId: { + type: 'string', + description: 'Polymarket token id for the order.', + }, + orderType: { + type: 'string', + enum: ['GTC', 'GTD', 'FOK', 'FAK'], + description: 'Order type, e.g. GTC, GTD, FOK, or FAK.', + }, + signedOrder: { + type: 'object', + description: + 'Signed order payload expected by the CLOB API /order endpoint.', + }, + }, + required: ['side', 'tokenId', 'orderType', 'signedOrder'], + }, + }, + { + type: 'function', + name: 'polymarket_clob_build_sign_and_place_order', + description: + 'Build an unsigned CLOB order, sign it with the runtime signer (EIP-712), and submit it.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + owner: { + type: ['string', 'null'], + description: + 'Optional CLOB API key owner override; defaults to POLYMARKET_CLOB_API_KEY.', + }, + side: { + type: 'string', + description: 'BUY or SELL.', + }, + tokenId: { + type: 'string', + description: 'Polymarket token id for the order.', + }, + orderType: { + type: 'string', + enum: ['GTC', 'GTD', 'FOK', 'FAK'], + description: 'Order type, e.g. GTC, GTD, FOK, or FAK.', + }, + makerAmount: { + type: 'string', + description: 'Order makerAmount in base units as an integer string.', + }, + takerAmount: { + type: 'string', + description: 'Order takerAmount in base units as an integer string.', + }, + maker: { + type: ['string', 'null'], + description: + 'Optional maker override. Must match runtime signer or POLYMARKET_CLOB_ADDRESS.', + }, + signer: { + type: ['string', 'null'], + description: + 'Optional signer override. Must match runtime signer or POLYMARKET_CLOB_ADDRESS.', + }, + taker: { + type: ['string', 'null'], + description: 'Optional taker address override (defaults to zero address).', + }, + expiration: { + type: ['string', 'null'], + description: 'Optional expiration timestamp as integer string. Default 0.', + }, + nonce: { + type: ['string', 'null'], + description: 'Optional nonce as integer string. Default 0.', + }, + feeRateBps: { + type: ['string', 'null'], + description: 'Optional fee rate in bps as integer string. Default 0.', + }, + signatureType: { + type: ['string', 'integer', 'null'], + description: + 'Optional signature type (EOA|POLY_GNOSIS_SAFE|POLY_PROXY or enum 0/1/2). Default EOA.', + }, + salt: { + type: ['string', 'null'], + description: 'Optional uint256 salt as integer string. Random if omitted.', + }, + exchange: { + type: ['string', 'null'], + description: + 'Optional CTF exchange address override for EIP-712 domain verifyingContract.', + }, + chainId: { + type: ['integer', 'null'], + description: + 'Optional chainId override for EIP-712 domain. Defaults to current RPC chain.', + }, + }, + required: ['side', 'tokenId', 'orderType', 'makerAmount', 'takerAmount'], + }, + }, + { + type: 'function', + name: 'polymarket_clob_cancel_orders', + description: + 'Cancel Polymarket CLOB orders by ids, by market, or cancel all open orders.', + strict: true, + parameters: { + type: 'object', + additionalProperties: false, + properties: { + mode: { + type: 'string', + enum: ['ids', 'market', 'all'], + description: 'ids | market | all', + }, + orderIds: { + type: ['array', 'null'], + items: { type: 'string' }, + description: 'Order ids required when mode=ids.', + }, + market: { + type: ['string', 'null'], + description: 'Market id used when mode=market.', + }, + assetId: { + type: ['string', 'null'], + description: 'Optional asset id used when mode=market.', + }, + }, + required: ['mode'], + }, + } + ); + } + + return tools; +} + +async function executeToolCalls({ + toolCalls, + publicClient, + walletClient, + account, + config, + ogContext, +}) { + const outputs = []; + const onchainToolsEnabled = config.proposeEnabled || config.disputeEnabled; + const hasPostProposal = toolCalls.some((call) => call.name === 'post_bond_and_propose'); + let builtTransactions; + + for (const call of toolCalls) { + const args = parseToolArguments(call.arguments); + if (!args) { + console.warn('[agent] Skipping tool call with invalid args:', call); + continue; + } + + if (call.name === 'build_og_transactions') { + try { + const transactions = buildOgTransactions(args.actions ?? [], { config }); + builtTransactions = transactions; + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ status: 'ok', transactions }), + }); + } catch (error) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'error', + message: error?.message ?? String(error), + }), + }); + } + continue; + } + + if (call.name === 'polymarket_clob_place_order') { + if (!config.polymarketClobEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'polymarket CLOB disabled', + }), + }); + continue; + } + + try { + const runtimeSignerAddress = getAddress(account.address); + const clobAuthAddress = config.polymarketClobAddress + ? getAddress(config.polymarketClobAddress) + : runtimeSignerAddress; + const normalizedSignedOrder = normalizeSignedOrderPayload(args.signedOrder); + if (!normalizedSignedOrder) { + throw new Error('signedOrder is required and must be an object.'); + } + const declaredSide = normalizeOrderSide(args.side); + if (!declaredSide) { + throw new Error('side must be BUY or SELL'); + } + if (!args.tokenId) { + throw new Error('tokenId is required'); + } + const declaredTokenId = String(args.tokenId).trim(); + const orderType = normalizeOrderType(args.orderType); + if (!orderType) { + throw new Error('orderType must be one of GTC, GTD, FOK, FAK'); + } + const { side: signedOrderSide, tokenId: signedOrderTokenId } = + extractSignedOrderSideAndTokenId(normalizedSignedOrder); + if (!signedOrderSide || !signedOrderTokenId) { + throw new Error( + 'signedOrder must include embedded side and token id (side + tokenId/asset_id).' + ); + } + if (signedOrderSide !== declaredSide) { + throw new Error( + `signedOrder side mismatch: declared ${declaredSide}, signed order has ${signedOrderSide}.` + ); + } + if (signedOrderTokenId !== declaredTokenId) { + throw new Error( + `signedOrder token mismatch: declared ${declaredTokenId}, signed order has ${signedOrderTokenId}.` + ); + } + const identityAddresses = + extractSignedOrderIdentityAddresses(normalizedSignedOrder); + if (identityAddresses.length === 0) { + throw new Error( + 'signedOrder must include an identity field (maker/signer/funder/user).' + ); + } + const allowedIdentityAddresses = new Set([ + clobAuthAddress, + runtimeSignerAddress, + ]); + const unauthorizedIdentities = identityAddresses.filter( + (address) => !allowedIdentityAddresses.has(address) + ); + if (unauthorizedIdentities.length > 0) { + throw new Error( + `signedOrder identity mismatch: expected only ${Array.from(allowedIdentityAddresses).join(', ')}, signed order contains unauthorized ${unauthorizedIdentities.join(', ')}.` + ); + } + const configuredOwnerApiKey = config.polymarketClobApiKey; + if (!configuredOwnerApiKey) { + throw new Error('Missing POLYMARKET_CLOB_API_KEY in runtime config.'); + } + const requestedOwner = + typeof args.owner === 'string' && args.owner.trim() + ? args.owner.trim() + : undefined; + if (requestedOwner && requestedOwner !== configuredOwnerApiKey) { + throw new Error( + 'owner mismatch: provided owner does not match configured POLYMARKET_CLOB_API_KEY.' + ); + } + const result = await placeClobOrder({ + config, + signingAddress: clobAuthAddress, + signedOrder: normalizedSignedOrder, + ownerApiKey: configuredOwnerApiKey, + orderType, + }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'submitted', + result, + }), + }); + } catch (error) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'error', + message: error?.message ?? String(error), + }), + }); + } + continue; + } + + if (call.name === 'polymarket_clob_cancel_orders') { + if (!config.polymarketClobEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'polymarket CLOB disabled', + }), + }); + continue; + } + + try { + const runtimeSignerAddress = getAddress(account.address); + const clobAuthAddress = config.polymarketClobAddress + ? getAddress(config.polymarketClobAddress) + : runtimeSignerAddress; + const mode = normalizeCancelMode(args.mode); + if (!mode) { + throw new Error('mode must be one of ids, market, all'); + } + const result = await cancelClobOrders({ + config, + signingAddress: clobAuthAddress, + mode, + orderIds: args.orderIds, + market: args.market, + assetId: args.assetId, + }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'submitted', + result, + }), + }); + } catch (error) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'error', + message: error?.message ?? String(error), + }), + }); + } + continue; + } + + if (call.name === 'polymarket_clob_build_sign_and_place_order') { + if (!config.polymarketClobEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'polymarket CLOB disabled', + }), + }); + continue; + } + + try { + const runtimeSignerAddress = getAddress(account.address); + const clobAuthAddress = config.polymarketClobAddress + ? getAddress(config.polymarketClobAddress) + : runtimeSignerAddress; + const declaredSide = normalizeOrderSide(args.side); + if (!declaredSide) { + throw new Error('side must be BUY or SELL'); + } + const declaredSideEnum = normalizeOrderSideEnumIndex(declaredSide); + if (declaredSideEnum === undefined) { + throw new Error('side must be BUY or SELL'); + } + if (!args.tokenId) { + throw new Error('tokenId is required'); + } + const declaredTokenId = String(args.tokenId).trim(); + const orderType = normalizeOrderType(args.orderType); + if (!orderType) { + throw new Error('orderType must be one of GTC, GTD, FOK, FAK'); + } + if (!args.makerAmount) { + throw new Error('makerAmount is required'); + } + if (!args.takerAmount) { + throw new Error('takerAmount is required'); + } + + const allowedIdentityAddresses = new Set([ + runtimeSignerAddress, + clobAuthAddress, + ]); + const maker = args.maker ? getAddress(String(args.maker)) : clobAuthAddress; + const signer = args.signer ? getAddress(String(args.signer)) : runtimeSignerAddress; + if (!allowedIdentityAddresses.has(maker)) { + throw new Error( + `maker identity mismatch: maker must be one of ${Array.from( + allowedIdentityAddresses + ).join(', ')}.` + ); + } + if (!allowedIdentityAddresses.has(signer)) { + throw new Error( + `signer identity mismatch: signer must be one of ${Array.from( + allowedIdentityAddresses + ).join(', ')}.` + ); + } + + const runtimeChainId = + typeof publicClient?.getChainId === 'function' + ? await publicClient.getChainId() + : undefined; + const chainId = Number(args.chainId ?? runtimeChainId); + if (!Number.isInteger(chainId) || chainId <= 0) { + throw new Error( + 'chainId is required to sign CLOB orders (provide chainId or use a client with getChainId).' + ); + } + const exchange = resolveClobExchangeAddress({ + chainId, + exchangeOverride: args.exchange ?? config.polymarketExchange, + }); + const normalizedSalt = normalizeOptionalUintString(args.salt, 'salt'); + const normalizedExpiration = normalizeOptionalUintString( + args.expiration, + 'expiration' + ); + const normalizedNonce = normalizeOptionalUintString(args.nonce, 'nonce'); + const normalizedFeeRateBps = normalizeOptionalUintString( + args.feeRateBps, + 'feeRateBps' + ); + const configuredSignatureType = + config.polymarketClobSignatureType !== undefined && + config.polymarketClobSignatureType !== null && + String(config.polymarketClobSignatureType).trim() !== '' + ? config.polymarketClobSignatureType + : undefined; + const requestedSignatureType = + args.signatureType !== undefined && + args.signatureType !== null && + String(args.signatureType).trim() !== '' + ? args.signatureType + : configuredSignatureType; + const unsignedOrder = buildClobOrderFromRaw({ + maker, + signer, + taker: args.taker, + tokenId: declaredTokenId, + makerAmount: args.makerAmount, + takerAmount: args.takerAmount, + side: declaredSideEnum, + signatureType: requestedSignatureType, + salt: normalizedSalt, + expiration: normalizedExpiration, + nonce: normalizedNonce, + feeRateBps: normalizedFeeRateBps, + }); + const signatureTypeIndex = Number(unsignedOrder.signatureType); + if (signatureTypeIndex !== 0) { + if (!config.polymarketClobAddress) { + throw new Error( + 'POLYMARKET_CLOB_ADDRESS is required for POLY_PROXY/POLY_GNOSIS_SAFE signature types.' + ); + } + if (maker !== clobAuthAddress) { + throw new Error( + 'maker must match POLYMARKET_CLOB_ADDRESS for POLY_PROXY/POLY_GNOSIS_SAFE signature types.' + ); + } + } + const signedOrder = await signClobOrder({ + walletClient, + account, + chainId, + exchange, + order: unsignedOrder, + }); + + const configuredOwnerApiKey = config.polymarketClobApiKey; + if (!configuredOwnerApiKey) { + throw new Error('Missing POLYMARKET_CLOB_API_KEY in runtime config.'); + } + const requestedOwner = + typeof args.owner === 'string' && args.owner.trim() + ? args.owner.trim() + : undefined; + if (requestedOwner && requestedOwner !== configuredOwnerApiKey) { + throw new Error( + 'owner mismatch: provided owner does not match configured POLYMARKET_CLOB_API_KEY.' + ); + } + const result = await placeClobOrder({ + config, + signingAddress: clobAuthAddress, + signedOrder, + ownerApiKey: configuredOwnerApiKey, + orderType, + }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'submitted', + signedOrder, + result, + }), + }); + } catch (error) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'error', + message: error?.message ?? String(error), + }), + }); + } + continue; + } + + if (call.name === 'make_deposit') { + if (!onchainToolsEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'onchain tools disabled', + }), + }); + continue; + } + const txHash = await makeDeposit({ + walletClient, + account, + config, + asset: args.asset, + amountWei: BigInt(args.amountWei), + }); + await publicClient.waitForTransactionReceipt({ hash: txHash }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'confirmed', + transactionHash: String(txHash), + }), + }); + continue; + } + + if (call.name === 'make_erc1155_deposit') { + if (!onchainToolsEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'onchain tools disabled', + }), + }); + continue; + } + const txHash = await makeErc1155Deposit({ + publicClient, + walletClient, + account, + config, + token: args.token, + tokenId: args.tokenId, + amount: args.amount, + data: args.data, + }); + await publicClient.waitForTransactionReceipt({ hash: txHash }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'confirmed', + transactionHash: String(txHash), + }), + }); + continue; + } + + if (call.name === 'post_bond_and_propose') { + if (!config.proposeEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'proposals disabled', + }), + }); + continue; + } + + const transactions = args.transactions.map((tx) => ({ + to: getAddress(tx.to), + value: BigInt(tx.value), + data: tx.data, + operation: Number(tx.operation), + })); + const result = await postBondAndPropose({ + publicClient, + walletClient, + account, + config, + ogModule: config.ogModule, + transactions, + }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'submitted', + ...result, + }), + }); + continue; + } + + if (call.name === 'dispute_assertion') { + if (!config.disputeEnabled) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'skipped', + reason: 'disputes disabled', + }), + }); + continue; + } + + try { + const result = await postBondAndDispute({ + publicClient, + walletClient, + account, + config, + ogContext, + assertionId: args.assertionId, + explanation: args.explanation, + }); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'submitted', + ...result, + }), + }); + } catch (error) { + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ + status: 'error', + message: error?.message ?? String(error), + }), + }); + } + continue; + } + + console.warn('[agent] Unknown tool call:', call.name); + outputs.push({ + callId: call.callId, + name: call.name, + output: safeStringify({ status: 'skipped', reason: 'unknown tool' }), + }); + } + + if (builtTransactions && !hasPostProposal) { + if (!config.proposeEnabled) { + console.log('[agent] Built transactions but proposals are disabled; skipping propose.'); + } else { + const result = await postBondAndPropose({ + publicClient, + walletClient, + account, + config, + ogModule: config.ogModule, + transactions: builtTransactions, + }); + outputs.push({ + callId: 'auto_post_bond_and_propose', + name: 'post_bond_and_propose', + output: safeStringify({ + status: 'submitted', + ...result, + }), + }); + } + } + + return outputs.filter((item) => item.callId); +} + +export { executeToolCalls, toolDefinitions }; diff --git a/agent/src/lib/tx.js b/agent/src/lib/tx.js new file mode 100644 index 00000000..458c8d59 --- /dev/null +++ b/agent/src/lib/tx.js @@ -0,0 +1,737 @@ +import { + decodeEventLog, + encodeFunctionData, + erc20Abi, + getAddress, + parseAbi, + stringToHex, + zeroAddress, +} from 'viem'; +import { + optimisticGovernorAbi, + optimisticOracleAbi, + transactionsProposedEvent, +} from './og.js'; +import { normalizeAssertion } from './og.js'; +import { + isPolymarketRelayerEnabled, + relayPolymarketTransaction, + resolveRelayerProxyWallet, +} from './polymarket-relayer.js'; +import { normalizeHashOrNull, summarizeViemError } from './utils.js'; + +const conditionalTokensAbi = parseAbi([ + 'function splitPosition(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)', + 'function mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)', + 'function redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] indexSets)', +]); + +const erc1155TransferAbi = parseAbi([ + 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)', +]); + +const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +function extractProposalHashFromReceipt({ receipt, ogModule }) { + if (!receipt?.logs || !Array.isArray(receipt.logs)) return null; + let normalizedOgModule; + try { + normalizedOgModule = getAddress(ogModule); + } catch (error) { + return null; + } + + for (const log of receipt.logs) { + let logAddress; + try { + logAddress = getAddress(log.address); + } catch (error) { + continue; + } + if (logAddress !== normalizedOgModule) continue; + + try { + const decoded = decodeEventLog({ + abi: [transactionsProposedEvent], + data: log.data, + topics: log.topics, + }); + const hash = normalizeHashOrNull(decoded?.args?.proposalHash); + if (hash) return hash; + } catch (error) { + // Ignore non-matching logs. + } + } + + return null; +} + +async function postBondAndPropose({ + publicClient, + walletClient, + account, + config, + ogModule, + transactions, +}) { + if (!config.proposeEnabled) { + throw new Error('Proposals disabled via PROPOSE_ENABLED.'); + } + + const normalizedTransactions = normalizeOgTransactions(transactions); + const proposerBalance = await publicClient.getBalance({ address: account.address }); + const [collateral, bondAmount, optimisticOracle] = await Promise.all([ + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'collateral', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'bondAmount', + }), + publicClient.readContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'optimisticOracleV3', + }), + ]); + let minimumBond = 0n; + try { + minimumBond = await publicClient.readContract({ + address: optimisticOracle, + abi: optimisticOracleAbi, + functionName: 'getMinimumBond', + args: [collateral], + }); + } catch (error) { + console.warn('[agent] Failed to fetch minimum bond from optimistic oracle:', error); + } + + const requiredBond = bondAmount > minimumBond ? bondAmount : minimumBond; + + if (requiredBond > 0n) { + const collateralBalance = await publicClient.readContract({ + address: collateral, + abi: erc20Abi, + functionName: 'balanceOf', + args: [account.address], + }); + if (collateralBalance < requiredBond) { + throw new Error( + `Insufficient bond collateral balance: need ${requiredBond.toString()} wei, have ${collateralBalance.toString()}.` + ); + } + const spenders = []; + if (config.bondSpender === 'og' || config.bondSpender === 'both') { + spenders.push(ogModule); + } + if (config.bondSpender === 'oo' || config.bondSpender === 'both') { + spenders.push(optimisticOracle); + } + + for (const spender of spenders) { + const approveHash = await walletClient.writeContract({ + address: collateral, + abi: erc20Abi, + functionName: 'approve', + args: [spender, requiredBond], + }); + await publicClient.waitForTransactionReceipt({ hash: approveHash }); + const allowance = await publicClient.readContract({ + address: collateral, + abi: erc20Abi, + functionName: 'allowance', + args: [account.address, spender], + }); + if (allowance < requiredBond) { + throw new Error( + `Insufficient bond allowance: need ${requiredBond.toString()} wei, have ${allowance.toString()} for spender ${spender}.` + ); + } + } + } + + if (proposerBalance === 0n) { + throw new Error( + `Proposer ${account.address} has 0 native balance; cannot pay gas to propose.` + ); + } + + let proposalTxHash; + let proposalHash; + const explanation = 'Agent serving Oya commitment.'; + const explanationBytes = stringToHex(explanation); + const proposalData = encodeFunctionData({ + abi: optimisticGovernorAbi, + functionName: 'proposeTransactions', + args: [normalizedTransactions, explanationBytes], + }); + let simulationError; + let submissionError; + try { + await publicClient.simulateContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'proposeTransactions', + args: [normalizedTransactions, explanationBytes], + account: account.address, + }); + } catch (error) { + simulationError = error; + const simulationMessage = + error?.shortMessage ?? error?.message ?? summarizeViemError(error)?.message ?? String(error); + console.warn('[agent] Proposal simulation failed:', simulationMessage); + if (!config.allowProposeOnSimulationFail) { + throw error; + } + console.warn('[agent] Simulation failed; attempting to propose anyway.'); + } + + try { + if (simulationError) { + proposalTxHash = await walletClient.sendTransaction({ + account, + to: ogModule, + data: proposalData, + value: 0n, + gas: config.proposeGasLimit, + }); + } else { + proposalTxHash = await walletClient.writeContract({ + address: ogModule, + abi: optimisticGovernorAbi, + functionName: 'proposeTransactions', + args: [normalizedTransactions, explanationBytes], + }); + } + } catch (error) { + submissionError = error; + const message = + error?.shortMessage ?? + error?.message ?? + simulationError?.shortMessage ?? + simulationError?.message ?? + String(error ?? simulationError); + console.warn('[agent] Propose submission failed:', message); + } + + if (proposalTxHash) { + console.log('[agent] Proposal submitted tx:', proposalTxHash); + try { + const receipt = await publicClient.waitForTransactionReceipt({ + hash: proposalTxHash, + }); + proposalHash = extractProposalHashFromReceipt({ + receipt, + ogModule, + }); + } catch (error) { + const reason = error?.shortMessage ?? error?.message ?? String(error); + console.warn('[agent] Failed to resolve OG proposalHash from receipt:', reason); + } + } + + if (proposalHash) { + console.log('[agent] OG proposal hash:', proposalHash); + } + + return { + transactionHash: proposalTxHash, + // Backward-compatible alias: legacy agents read `proposalHash` as submission tx hash. + proposalHash: proposalTxHash, + // New explicit OG proposal hash extracted from TransactionsProposed logs. + ogProposalHash: proposalHash, + bondAmount, + collateral, + optimisticOracle, + submissionError: submissionError ? summarizeViemError(submissionError) : null, + }; +} + +async function postBondAndDispute({ + publicClient, + walletClient, + account, + config, + ogContext, + assertionId, + explanation, +}) { + if (!config.disputeEnabled) { + throw new Error('Disputes disabled via DISPUTE_ENABLED.'); + } + + const proposerBalance = await publicClient.getBalance({ address: account.address }); + if (proposerBalance === 0n) { + throw new Error( + `Disputer ${account.address} has 0 native balance; cannot pay gas to dispute.` + ); + } + + const optimisticOracle = ogContext?.optimisticOracle; + if (!optimisticOracle) { + throw new Error('Missing optimistic oracle address.'); + } + + const assertionRaw = await publicClient.readContract({ + address: optimisticOracle, + abi: optimisticOracleAbi, + functionName: 'getAssertion', + args: [assertionId], + }); + const assertion = normalizeAssertion(assertionRaw); + + const nowBlock = await publicClient.getBlock(); + const now = BigInt(nowBlock.timestamp); + const expirationTime = BigInt(assertion.expirationTime ?? 0); + const disputer = assertion.disputer ? getAddress(assertion.disputer) : zeroAddress; + const settled = Boolean(assertion.settled); + if (settled) { + throw new Error(`Assertion ${assertionId} already settled.`); + } + if (expirationTime !== 0n && now >= expirationTime) { + throw new Error(`Assertion ${assertionId} expired at ${expirationTime}.`); + } + if (disputer !== zeroAddress) { + throw new Error(`Assertion ${assertionId} already disputed by ${disputer}.`); + } + + const bond = BigInt(assertion.bond ?? 0); + const currency = assertion.currency ? getAddress(assertion.currency) : zeroAddress; + if (currency === zeroAddress) { + throw new Error('Assertion currency is zero address; cannot post bond.'); + } + + if (bond > 0n) { + const collateralBalance = await publicClient.readContract({ + address: currency, + abi: erc20Abi, + functionName: 'balanceOf', + args: [account.address], + }); + if (collateralBalance < bond) { + throw new Error( + `Insufficient dispute bond balance: need ${bond.toString()} wei, have ${collateralBalance.toString()}.` + ); + } + + const approveHash = await walletClient.writeContract({ + address: currency, + abi: erc20Abi, + functionName: 'approve', + args: [optimisticOracle, bond], + }); + await publicClient.waitForTransactionReceipt({ hash: approveHash }); + } + + let disputeHash; + try { + await publicClient.simulateContract({ + address: optimisticOracle, + abi: optimisticOracleAbi, + functionName: 'disputeAssertion', + args: [assertionId, account.address], + account: account.address, + }); + disputeHash = await walletClient.writeContract({ + address: optimisticOracle, + abi: optimisticOracleAbi, + functionName: 'disputeAssertion', + args: [assertionId, account.address], + }); + } catch (error) { + const message = error?.shortMessage ?? error?.message ?? String(error); + throw new Error(`Dispute submission failed: ${message}`); + } + + if (explanation) { + console.log(`[agent] Dispute rationale: ${explanation}`); + } + + console.log('[agent] Dispute submitted:', disputeHash); + + return { + disputeHash, + bondAmount: bond, + collateral: currency, + optimisticOracle, + }; +} + +function normalizeOgTransactions(transactions) { + if (!Array.isArray(transactions)) { + throw new Error('transactions must be an array'); + } + + return transactions.map((tx, index) => { + if (!tx || !tx.to) { + throw new Error(`transactions[${index}] missing to`); + } + + return { + to: getAddress(tx.to), + value: BigInt(tx.value ?? 0), + data: tx.data ?? '0x', + operation: Number(tx.operation ?? 0), + }; + }); +} + +function buildOgTransactions(actions, options = {}) { + if (!Array.isArray(actions) || actions.length === 0) { + throw new Error('actions must be a non-empty array'); + } + + const config = options.config ?? {}; + + const transactions = []; + + for (const action of actions) { + const operation = action.operation !== undefined ? Number(action.operation) : 0; + + if (action.kind === 'erc20_transfer') { + if (!action.token || !action.to || action.amountWei === undefined) { + throw new Error('erc20_transfer requires token, to, amountWei'); + } + + const data = encodeFunctionData({ + abi: erc20Abi, + functionName: 'transfer', + args: [getAddress(action.to), BigInt(action.amountWei)], + }); + + transactions.push({ + to: getAddress(action.token), + value: '0', + data, + operation, + }); + continue; + } + + if (action.kind === 'native_transfer') { + if (!action.to || action.amountWei === undefined) { + throw new Error('native_transfer requires to, amountWei'); + } + + transactions.push({ + to: getAddress(action.to), + value: BigInt(action.amountWei).toString(), + data: '0x', + operation, + }); + continue; + } + + if (action.kind === 'contract_call') { + if (!action.to || !action.abi) { + throw new Error('contract_call requires to, abi'); + } + + const abi = parseAbi([`function ${action.abi}`]); + const args = Array.isArray(action.args) ? action.args : []; + const data = encodeFunctionData({ + abi, + functionName: action.abi.split('(')[0], + args, + }); + const value = action.valueWei !== undefined ? BigInt(action.valueWei).toString() : '0'; + + transactions.push({ + to: getAddress(action.to), + value, + data, + operation, + }); + continue; + } + + if (action.kind === 'uniswap_v3_exact_input_single') { + if ( + !action.router || + !action.tokenIn || + !action.tokenOut || + action.fee === undefined || + !action.recipient || + action.amountInWei === undefined || + action.amountOutMinWei === undefined + ) { + throw new Error( + 'uniswap_v3_exact_input_single requires router, tokenIn, tokenOut, fee, recipient, amountInWei, amountOutMinWei' + ); + } + + const router = getAddress(action.router); + const tokenIn = getAddress(action.tokenIn); + const tokenOut = getAddress(action.tokenOut); + const recipient = getAddress(action.recipient); + const fee = Number(action.fee); + + const approveData = encodeFunctionData({ + abi: erc20Abi, + functionName: 'approve', + args: [router, BigInt(action.amountInWei)], + }); + transactions.push({ + to: tokenIn, + value: '0', + data: approveData, + operation, + }); + + const swapAbi = parseAbi([ + 'function exactInputSingle((address tokenIn,address tokenOut,uint24 fee,address recipient,uint256 amountIn,uint256 amountOutMinimum,uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)', + ]); + const swapData = encodeFunctionData({ + abi: swapAbi, + functionName: 'exactInputSingle', + args: [ + { + tokenIn, + tokenOut, + fee, + recipient, + amountIn: BigInt(action.amountInWei), + amountOutMinimum: BigInt(action.amountOutMinWei), + sqrtPriceLimitX96: BigInt(action.sqrtPriceLimitX96 ?? 0), + }, + ], + }); + transactions.push({ + to: router, + value: '0', + data: swapData, + operation, + }); + continue; + } + + if (action.kind === 'ctf_split' || action.kind === 'ctf_merge') { + if (!action.collateralToken || !action.conditionId || action.amount === undefined) { + throw new Error(`${action.kind} requires collateralToken, conditionId, amount`); + } + const collateralToken = getAddress(action.collateralToken); + const ctfContract = action.ctfContract + ? getAddress(action.ctfContract) + : config.polymarketConditionalTokens; + if (!ctfContract) { + throw new Error(`${action.kind} requires ctfContract or POLYMARKET_CONDITIONAL_TOKENS`); + } + const parentCollectionId = action.parentCollectionId ?? ZERO_BYTES32; + const partition = Array.isArray(action.partition) && action.partition.length > 0 + ? action.partition.map((value) => BigInt(value)) + : [1n, 2n]; + const amount = BigInt(action.amount); + if (amount <= 0n) { + throw new Error(`${action.kind} amount must be > 0`); + } + + if (action.kind === 'ctf_split') { + // Use zero-first approval for compatibility with ERC20 tokens that + // require allowance reset before setting a new non-zero allowance. + const resetApproveData = encodeFunctionData({ + abi: erc20Abi, + functionName: 'approve', + args: [ctfContract, 0n], + }); + transactions.push({ + to: collateralToken, + value: '0', + data: resetApproveData, + operation: 0, + }); + + const approveData = encodeFunctionData({ + abi: erc20Abi, + functionName: 'approve', + args: [ctfContract, amount], + }); + transactions.push({ + to: collateralToken, + value: '0', + data: approveData, + operation: 0, + }); + } + + const functionName = action.kind === 'ctf_split' ? 'splitPosition' : 'mergePositions'; + const data = encodeFunctionData({ + abi: conditionalTokensAbi, + functionName, + args: [ + collateralToken, + parentCollectionId, + action.conditionId, + partition, + amount, + ], + }); + + transactions.push({ + to: ctfContract, + value: '0', + data, + operation: 0, + }); + continue; + } + + if (action.kind === 'ctf_redeem') { + if (!action.collateralToken || !action.conditionId) { + throw new Error('ctf_redeem requires collateralToken and conditionId'); + } + const ctfContract = action.ctfContract + ? getAddress(action.ctfContract) + : config.polymarketConditionalTokens; + if (!ctfContract) { + throw new Error('ctf_redeem requires ctfContract or POLYMARKET_CONDITIONAL_TOKENS'); + } + const parentCollectionId = action.parentCollectionId ?? ZERO_BYTES32; + const indexSets = Array.isArray(action.indexSets) && action.indexSets.length > 0 + ? action.indexSets.map((value) => BigInt(value)) + : [1n, 2n]; + + const data = encodeFunctionData({ + abi: conditionalTokensAbi, + functionName: 'redeemPositions', + args: [ + getAddress(action.collateralToken), + parentCollectionId, + action.conditionId, + indexSets, + ], + }); + + transactions.push({ + to: ctfContract, + value: '0', + data, + operation: 0, + }); + continue; + } + + throw new Error(`Unknown action kind: ${action.kind}`); + } + + return transactions; +} + +async function makeDeposit({ + walletClient, + account, + config, + asset, + amountWei, +}) { + const depositAsset = asset ? getAddress(asset) : config.defaultDepositAsset; + const depositAmount = + amountWei !== undefined ? amountWei : config.defaultDepositAmountWei; + + if (!depositAsset || depositAmount === undefined) { + throw new Error('Deposit requires asset and amount (wei).'); + } + + if (depositAsset === zeroAddress) { + return walletClient.sendTransaction({ + account, + to: config.commitmentSafe, + value: BigInt(depositAmount), + }); + } + + return walletClient.writeContract({ + address: depositAsset, + abi: erc20Abi, + functionName: 'transfer', + args: [config.commitmentSafe, BigInt(depositAmount)], + }); +} + +async function makeErc1155Deposit({ + publicClient, + walletClient, + account, + config, + token, + tokenId, + amount, + data, +}) { + if (!token || tokenId === undefined || amount === undefined) { + throw new Error('ERC1155 deposit requires token, tokenId, and amount.'); + } + + const normalizedToken = getAddress(token); + const normalizedTokenId = BigInt(tokenId); + const normalizedAmount = BigInt(amount); + if (normalizedAmount <= 0n) { + throw new Error('ERC1155 deposit amount must be > 0.'); + } + + const transferData = data ?? '0x'; + if (isPolymarketRelayerEnabled(config)) { + let relayerFromAddress = config?.polymarketRelayerFromAddress; + if (!relayerFromAddress) { + const resolved = await resolveRelayerProxyWallet({ + publicClient, + account, + config, + }); + relayerFromAddress = resolved.proxyWallet; + } + const transferCallData = encodeFunctionData({ + abi: erc1155TransferAbi, + functionName: 'safeTransferFrom', + args: [ + getAddress(relayerFromAddress), + config.commitmentSafe, + normalizedTokenId, + normalizedAmount, + transferData, + ], + }); + const relayed = await relayPolymarketTransaction({ + publicClient, + walletClient, + account, + config, + proxyWallet: relayerFromAddress, + to: normalizedToken, + data: transferCallData, + value: 0n, + operation: 0, + metadata: { + tool: 'make_erc1155_deposit', + token: normalizedToken, + tokenId: normalizedTokenId.toString(), + amount: normalizedAmount.toString(), + }, + }); + return relayed.transactionHash; + } + + return walletClient.writeContract({ + address: normalizedToken, + abi: erc1155TransferAbi, + functionName: 'safeTransferFrom', + args: [ + account.address, + config.commitmentSafe, + normalizedTokenId, + normalizedAmount, + transferData, + ], + }); +} + +export { + buildOgTransactions, + makeErc1155Deposit, + makeDeposit, + normalizeOgTransactions, + postBondAndDispute, + postBondAndPropose, +}; diff --git a/agent/src/lib/uniswapV3Price.js b/agent/src/lib/uniswapV3Price.js new file mode 100644 index 00000000..a902cead --- /dev/null +++ b/agent/src/lib/uniswapV3Price.js @@ -0,0 +1,295 @@ +import { erc20Abi, getAddress, parseAbi, zeroAddress } from 'viem'; + +const uniswapV3PoolAbi = parseAbi([ + 'function token0() view returns (address)', + 'function token1() view returns (address)', + 'function fee() view returns (uint24)', + 'function liquidity() view returns (uint128)', + 'function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)', +]); + +const uniswapV3FactoryAbi = parseAbi([ + 'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address)', +]); + +const defaultFactoryByChainId = new Map([ + [1, '0x1F98431c8aD98523631AE4a59f267346ea31F984'], + [11155111, '0x0227628f3F023bb0B980b67D528571c95c6DaC1c'], +]); + +async function getFactoryAddress({ publicClient, configuredFactory }) { + if (configuredFactory) return configuredFactory; + const chainId = await publicClient.getChainId(); + const factory = defaultFactoryByChainId.get(chainId); + if (!factory) { + throw new Error( + `No Uniswap V3 factory configured for chainId ${chainId}. Set UNISWAP_V3_FACTORY.` + ); + } + return getAddress(factory); +} + +async function loadPoolMeta({ publicClient, pool, tokenMetaCache, poolMetaCache }) { + const cached = poolMetaCache.get(pool); + if (cached) return cached; + + const [token0, token1, fee] = await Promise.all([ + publicClient.readContract({ + address: pool, + abi: uniswapV3PoolAbi, + functionName: 'token0', + }), + publicClient.readContract({ + address: pool, + abi: uniswapV3PoolAbi, + functionName: 'token1', + }), + publicClient.readContract({ + address: pool, + abi: uniswapV3PoolAbi, + functionName: 'fee', + }), + ]); + + const [normalizedToken0, normalizedToken1] = [getAddress(token0), getAddress(token1)]; + + const ensureDecimals = async (token) => { + if (tokenMetaCache.has(token)) return tokenMetaCache.get(token); + const decimals = Number( + await publicClient.readContract({ + address: token, + abi: erc20Abi, + functionName: 'decimals', + }) + ); + tokenMetaCache.set(token, { decimals }); + return tokenMetaCache.get(token); + }; + + await Promise.all([ensureDecimals(normalizedToken0), ensureDecimals(normalizedToken1)]); + + const meta = { + token0: normalizedToken0, + token1: normalizedToken1, + fee: Number(fee), + }; + poolMetaCache.set(pool, meta); + return meta; +} + +async function resolvePoolForTrigger({ + publicClient, + trigger, + config, + resolvedPoolCache, +}) { + const baseToken = getAddress(trigger.baseToken); + const quoteToken = getAddress(trigger.quoteToken); + const cacheKey = `${baseToken}:${quoteToken}:${trigger.pool ?? trigger.poolSelection ?? ''}`; + if (resolvedPoolCache.has(cacheKey)) { + return resolvedPoolCache.get(cacheKey); + } + + if (trigger.pool) { + const resolved = { pool: getAddress(trigger.pool) }; + resolvedPoolCache.set(cacheKey, resolved); + return resolved; + } + + if (trigger.poolSelection !== 'high-liquidity') { + throw new Error( + `Trigger ${trigger.id} must provide pool or poolSelection=high-liquidity` + ); + } + + const factory = await getFactoryAddress({ + publicClient, + configuredFactory: config.uniswapV3Factory, + }); + + let best = null; + for (const feeTier of config.uniswapV3FeeTiers ?? [500, 3000, 10000]) { + const pool = await publicClient.readContract({ + address: factory, + abi: uniswapV3FactoryAbi, + functionName: 'getPool', + args: [baseToken, quoteToken, Number(feeTier)], + }); + + if (!pool || getAddress(pool) === zeroAddress) { + continue; + } + + const normalizedPool = getAddress(pool); + const liquidity = await publicClient.readContract({ + address: normalizedPool, + abi: uniswapV3PoolAbi, + functionName: 'liquidity', + }); + + if (!best || BigInt(liquidity) > best.liquidity) { + best = { + pool: normalizedPool, + liquidity: BigInt(liquidity), + }; + } + } + + if (!best) { + throw new Error( + `No Uniswap V3 pool found for ${baseToken}/${quoteToken} across fee tiers.` + ); + } + + const resolved = { pool: best.pool }; + resolvedPoolCache.set(cacheKey, resolved); + return resolved; +} + +function quotePerBaseFromSqrtPriceX96({ sqrtPriceX96, token0Decimals, token1Decimals, baseIsToken0 }) { + const sqrt = Number(sqrtPriceX96); + if (!Number.isFinite(sqrt) || sqrt <= 0) { + throw new Error('Invalid sqrtPriceX96 from pool slot0.'); + } + + const q192 = 2 ** 192; + const rawToken1PerToken0 = (sqrt * sqrt) / q192; + + if (baseIsToken0) { + return rawToken1PerToken0 * 10 ** (token0Decimals - token1Decimals); + } + + if (rawToken1PerToken0 === 0) { + throw new Error('Pool price resolved to zero.'); + } + + return (1 / rawToken1PerToken0) * 10 ** (token1Decimals - token0Decimals); +} + +function evaluateComparator({ comparator, price, threshold }) { + if (comparator === 'gte') return price >= threshold; + if (comparator === 'lte') return price <= threshold; + throw new Error(`Unsupported comparator: ${comparator}`); +} + +async function collectPriceTriggerSignals({ + publicClient, + config, + triggers, + nowMs, + triggerState, + tokenMetaCache, + poolMetaCache, + resolvedPoolCache, +}) { + if (!Array.isArray(triggers) || triggers.length === 0) { + return []; + } + + const evaluations = []; + + for (const trigger of triggers) { + const triggerId = trigger && typeof trigger === 'object' && trigger.id !== undefined + ? String(trigger.id) + : 'unknown-trigger'; + if (!trigger || typeof trigger !== 'object') { + console.warn(`[agent] Price trigger ${triggerId} skipped: malformed trigger entry.`); + continue; + } + try { + const baseToken = getAddress(trigger.baseToken); + const quoteToken = getAddress(trigger.quoteToken); + + const resolved = await resolvePoolForTrigger({ + publicClient, + trigger, + config, + resolvedPoolCache, + }); + const pool = resolved.pool; + + const poolMeta = await loadPoolMeta({ + publicClient, + pool, + tokenMetaCache, + poolMetaCache, + }); + + const baseIsToken0 = poolMeta.token0 === baseToken && poolMeta.token1 === quoteToken; + const baseIsToken1 = poolMeta.token1 === baseToken && poolMeta.token0 === quoteToken; + + if (!baseIsToken0 && !baseIsToken1) { + console.warn( + `[agent] Price trigger ${trigger.id} skipped: pool ${pool} does not match base/quote tokens.` + ); + continue; + } + + const slot0 = await publicClient.readContract({ + address: pool, + abi: uniswapV3PoolAbi, + functionName: 'slot0', + }); + + const token0Meta = tokenMetaCache.get(poolMeta.token0); + const token1Meta = tokenMetaCache.get(poolMeta.token1); + + const price = quotePerBaseFromSqrtPriceX96({ + sqrtPriceX96: slot0[0], + token0Decimals: token0Meta.decimals, + token1Decimals: token1Meta.decimals, + baseIsToken0, + }); + + const matches = evaluateComparator({ + comparator: trigger.comparator, + price, + threshold: trigger.threshold, + }); + + const prior = triggerState.get(trigger.id) ?? { + fired: false, + lastMatched: false, + }; + + const shouldEmit = matches && (!prior.lastMatched || (!trigger.emitOnce && !prior.fired)); + + triggerState.set(trigger.id, { + fired: prior.fired || (matches && trigger.emitOnce), + lastMatched: matches, + }); + + if (!shouldEmit || (trigger.emitOnce && prior.fired)) { + continue; + } + + evaluations.push({ + kind: 'priceTrigger', + triggerId: trigger.id, + triggerLabel: trigger.label, + priority: trigger.priority ?? 0, + pool, + poolFee: poolMeta.fee, + baseToken, + quoteToken, + comparator: trigger.comparator, + threshold: trigger.threshold, + observedPrice: price, + triggerTimestampMs: nowMs, + }); + } catch (error) { + console.warn(`[agent] Price trigger ${triggerId} skipped:`, error?.message ?? error); + continue; + } + } + + evaluations.sort((a, b) => { + const priorityOrder = Number(a.priority) - Number(b.priority); + if (priorityOrder !== 0) return priorityOrder; + return String(a.triggerId).localeCompare(String(b.triggerId)); + }); + + return evaluations; +} + +export { collectPriceTriggerSignals }; diff --git a/agent/src/lib/utils.js b/agent/src/lib/utils.js new file mode 100644 index 00000000..c74c0c94 --- /dev/null +++ b/agent/src/lib/utils.js @@ -0,0 +1,124 @@ +import { decodeFunctionData, erc20Abi, getAddress } from 'viem'; + +function mustGetEnv(key) { + const value = process.env[key]; + if (!value) { + throw new Error(`Missing required env var ${key}`); + } + return value; +} + +function normalizePrivateKey(value) { + if (!value) return value; + return value.startsWith('0x') ? value : `0x${value}`; +} + +function parseAddressList(list) { + if (!list) return []; + return list + .split(',') + .map((value) => value.trim()) + .filter(Boolean) + .map(getAddress); +} + +function summarizeViemError(error) { + if (!error) return null; + + return { + name: error.name, + shortMessage: error.shortMessage, + message: error.message, + details: error.details, + metaMessages: error.metaMessages, + data: error.data ?? error.cause?.data, + cause: error.cause?.shortMessage ?? error.cause?.message ?? error.cause, + }; +} + +function normalizeAddressOrNull(value, { trim = true, requireHex = true } = {}) { + if (typeof value !== 'string') return null; + const candidate = trim ? value.trim() : value; + if (candidate.length !== 42 || !candidate.startsWith('0x')) return null; + if (requireHex && !/^0x[0-9a-fA-F]{40}$/.test(candidate)) return null; + return candidate.toLowerCase(); +} + +function normalizeAddressOrThrow(value, options = {}) { + const normalized = normalizeAddressOrNull(value, { trim: false, ...options }); + if (!normalized) { + throw new Error(`Invalid address: ${value}`); + } + return normalized; +} + +function normalizeHashOrNull(value) { + if (typeof value !== 'string') return null; + const trimmed = value.trim(); + if (!/^0x[0-9a-fA-F]{64}$/.test(trimmed)) return null; + return trimmed.toLowerCase(); +} + +function normalizeTokenId(value) { + if (value === null || value === undefined || value === '') return null; + try { + const normalized = BigInt(value); + if (normalized < 0n) return null; + return normalized.toString(); + } catch (error) { + return null; + } +} + +function parseFiniteNumber(value) { + const parsed = Number(value); + if (!Number.isFinite(parsed)) return null; + return parsed; +} + +function decodeErc20TransferCallData(data) { + if (typeof data !== 'string') return null; + + try { + const decoded = decodeFunctionData({ + abi: erc20Abi, + data, + }); + if (decoded.functionName !== 'transfer') return null; + + const to = normalizeAddressOrNull(decoded.args?.[0], { trim: false }); + if (!to) return null; + const amount = BigInt(decoded.args?.[1] ?? 0n); + if (amount < 0n) return null; + return { to, amount }; + } catch (error) { + return null; + } +} + +function parseToolArguments(raw) { + if (!raw) return null; + if (typeof raw === 'object') return raw; + if (typeof raw === 'string') { + try { + return JSON.parse(raw); + } catch (error) { + return null; + } + } + return null; +} + +export { + decodeErc20TransferCallData, + mustGetEnv, + normalizeAddressOrNull, + normalizeAddressOrThrow, + normalizeHashOrNull, + normalizeTokenId, + normalizePrivateKey, + parseFiniteNumber, + parseAddressList, + parseToolArguments, + summarizeViemError, +}; diff --git a/agent/with-signer.mjs b/agent/with-signer.mjs new file mode 100644 index 00000000..2ae21874 --- /dev/null +++ b/agent/with-signer.mjs @@ -0,0 +1,156 @@ +#!/usr/bin/env node +import { readFile } from 'node:fs/promises'; +import { execFile, spawn } from 'node:child_process'; +import { promisify } from 'node:util'; +import { Wallet } from 'ethers'; + +const execFileAsync = promisify(execFile); + +function normalizePrivateKey(value) { + if (!value) return value; + return value.startsWith('0x') ? value : `0x${value}`; +} + +function mustGetEnv(key) { + const value = process.env[key]; + if (!value) { + throw new Error(`Missing required env var ${key}`); + } + return value; +} + +async function loadPrivateKeyFromKeystore() { + const keystorePath = mustGetEnv('KEYSTORE_PATH'); + const keystorePassword = mustGetEnv('KEYSTORE_PASSWORD'); + const keystoreJson = await readFile(keystorePath, 'utf8'); + const wallet = await Wallet.fromEncryptedJson(keystoreJson, keystorePassword); + return wallet.privateKey; +} + +async function loadPrivateKeyFromKeychain() { + const service = mustGetEnv('KEYCHAIN_SERVICE'); + const account = mustGetEnv('KEYCHAIN_ACCOUNT'); + + if (process.platform === 'darwin') { + const { stdout } = await execFileAsync('security', [ + 'find-generic-password', + '-s', + service, + '-a', + account, + '-w', + ]); + return stdout.trim(); + } + + if (process.platform === 'linux') { + const { stdout } = await execFileAsync('secret-tool', [ + 'lookup', + 'service', + service, + 'account', + account, + ]); + return stdout.trim(); + } + + throw new Error('Keychain lookup not supported on this platform.'); +} + +async function loadPrivateKeyFromVault() { + const vaultAddr = mustGetEnv('VAULT_ADDR').replace(/\/+$/, ''); + const vaultToken = mustGetEnv('VAULT_TOKEN'); + const vaultPath = mustGetEnv('VAULT_SECRET_PATH').replace(/^\/+/, ''); + const vaultNamespace = process.env.VAULT_NAMESPACE; + const vaultKeyField = process.env.VAULT_SECRET_KEY ?? 'private_key'; + + const response = await fetch(`${vaultAddr}/v1/${vaultPath}`, { + headers: { + 'X-Vault-Token': vaultToken, + ...(vaultNamespace ? { 'X-Vault-Namespace': vaultNamespace } : {}), + }, + }); + + if (!response.ok) { + throw new Error(`Vault request failed (${response.status}).`); + } + + const payload = await response.json(); + const data = payload?.data?.data ?? payload?.data ?? {}; + const value = data[vaultKeyField]; + if (!value) { + throw new Error(`Vault secret missing key '${vaultKeyField}'.`); + } + + return value; +} + +async function resolvePrivateKey() { + const signerType = (process.env.SIGNER_TYPE ?? 'env').toLowerCase(); + + if (signerType === 'env') { + return mustGetEnv('PRIVATE_KEY'); + } + + if (signerType === 'keystore') { + return loadPrivateKeyFromKeystore(); + } + + if (signerType === 'keychain') { + return loadPrivateKeyFromKeychain(); + } + + if (signerType === 'vault') { + return loadPrivateKeyFromVault(); + } + + throw new Error(`Signer type '${signerType}' does not expose a private key.`); +} + +function parseArgs(args) { + const options = { envVar: 'DEPLOYER_PK' }; + const separatorIndex = args.indexOf('--'); + if (separatorIndex === -1) { + throw new Error('Usage: node agent/with-signer.mjs [--env VAR] -- '); + } + + const optionArgs = args.slice(0, separatorIndex); + const commandArgs = args.slice(separatorIndex + 1); + + for (let i = 0; i < optionArgs.length; i += 1) { + if (optionArgs[i] === '--env') { + options.envVar = optionArgs[i + 1]; + i += 1; + continue; + } + } + + if (!options.envVar) { + throw new Error('Missing env var name for --env.'); + } + + if (commandArgs.length === 0) { + throw new Error('Missing command after --.'); + } + + return { options, commandArgs }; +} + +const { options, commandArgs } = parseArgs(process.argv.slice(2)); +const privateKey = normalizePrivateKey(await resolvePrivateKey()); + +const [command, ...commandRest] = commandArgs; +const child = await new Promise((resolve, reject) => { + const proc = spawn(command, commandRest, { + stdio: 'inherit', + env: { + ...process.env, + [options.envVar]: privateKey, + }, + }); + + proc.on('exit', (code) => resolve(code ?? 1)); + proc.on('error', reject); +}); + +process.exitCode = child; diff --git a/docs/agent-extension-guidelines.md b/docs/agent-extension-guidelines.md new file mode 100644 index 00000000..4ba8a2cd --- /dev/null +++ b/docs/agent-extension-guidelines.md @@ -0,0 +1,49 @@ +# Agent Extension Guidelines + +This document defines where new behavior should be implemented when adding or updating an agent. + +## Core Rule + +Implement agent-specific behavior in that agent's own files under `agent-library/agents//`. + +Do not place agent-specific logic in shared generalized runner files unless the change is clearly cross-agent. + +## Decision Tree + +1. Does the behavior apply to exactly one agent? +Yes: implement in `agent-library/agents//`. +No: continue. +2. Does the behavior represent reusable infrastructure needed by multiple agents? +Yes: implement in shared files (`agent/src/lib/*` or `agent/src/index.js`) with compatibility checks. +No: keep it agent-local. +3. Is this a bug in shared infrastructure affecting multiple agents? +Yes: patch shared code and note impacted modules in the PR. +No: keep it agent-local. + +## Allowed Agent-Local Changes + +- Prompt logic and tool-choice strategy in a single agent. +- Parsing rules unique to one commitment format. +- Agent-specific scheduling or timelock behavior. +- Agent-specific metadata generation, tests, and fixtures. + +## Allowed Shared-Runner Changes + +- Common transport/signer/config helpers used by multiple agents. +- Shared proposal/dispute plumbing with no commitment-specific assumptions. +- Defect fixes in existing shared logic that impact more than one agent. + +## Anti-Patterns + +- Adding `if (agentName === "...")` branches in shared runner code for new behavior. +- Hardcoding commitment-specific policy in `agent/src/lib/`. +- Reusing shared modules as a shortcut for single-agent feature work. + +## Pull Request Checklist + +- [ ] Agent-specific behavior is implemented in `agent-library/agents//`. +- [ ] Shared runner files were changed only for cross-agent infrastructure or shared bug fixes. +- [ ] If shared files changed, PR includes: +Rationale for why agent-local implementation was insufficient. +List of existing agents affected. +- [ ] Relevant tests/simulations were run and listed. diff --git a/docs/agent.md b/docs/agent.md new file mode 100644 index 00000000..c3495509 --- /dev/null +++ b/docs/agent.md @@ -0,0 +1,26 @@ +# Offchain Agent + +The agent in `agent/` can propose and execute transactions via the Optimistic Governor module. Customize the decision logic, signal monitoring, and overall behavior to match your commitment rules. + +## Setup + +```shell +cd agent +npm install +cp .env.example .env +npm start +``` + +Fill in at least: + +- `RPC_URL` +- `PRIVATE_KEY` +- `COMMITMENT_SAFE` +- `OG_MODULE` +- `WATCH_ASSETS` + +## Built-In Tools + +- `postBondAndPropose` +- `makeDeposit` +- `pollCommitmentChanges` diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 00000000..cf3a53b9 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,71 @@ +# Deployment and Configuration + +## Deploy a Commitment + +```shell +forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url \ + --broadcast \ + --private-key +``` + +## Required Environment Variables + +- `DEPLOYER_PK`: Private key for the deployer. +- `OG_COLLATERAL`: Address of the ERC20 collateral token. +- `OG_BOND_AMOUNT`: Bond amount for challenges. +- `OG_RULES`: Plain-language rules for the commitment. + +## Optional Overrides + +- `SAFE_SALT_NONCE`, `SAFE_THRESHOLD`, `SAFE_OWNERS` +- `OG_SALT_NONCE`, `OG_CHALLENGE_PERIOD`, `OG_RULES_URI` +- `OG_MASTER_COPY`, `SAFE_SINGLETON`, `SAFE_FALLBACK_HANDLER` +- `MODULE_PROXY_FACTORY` + +## Example `.env` + +```ini +# Required +DEPLOYER_PK=0xabc123... +OG_COLLATERAL=0x1111111111111111111111111111111111111111 +OG_BOND_AMOUNT=250000000 +OG_RULES="Any assets deposited in this Commitment may be transferred back to the depositor before January 15th, 2026 (12:00AM PST). After the deadline, assets may only be transferred to jdshutt.eth. If a third party is initiating the transfer after the deadline, they may take a 10% cut of the assets being transferred as a fee." + +# Safe overrides +SAFE_OWNERS=0x2222222222222222222222222222222222222222,0x3333333333333333333333333333333333333333 +SAFE_THRESHOLD=2 +SAFE_SALT_NONCE=12345 + +# Optimistic Governor overrides +OG_CHALLENGE_PERIOD=604800 +OG_RULES_URI=ipfs://bafy... +OG_SALT_NONCE=67890 + +# Optional factory / master copy overrides +MODULE_PROXY_FACTORY=0x4444444444444444444444444444444444444444 +OG_MASTER_COPY=0x5555555555555555555555555555555555555555 +SAFE_SINGLETON=0x6666666666666666666666666666666666666666 +SAFE_FALLBACK_HANDLER=0x7777777777777777777777777777777777777777 +``` + +## Local Testing (Anvil) + +Dry-run (no broadcast): + +```shell +anvil +forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url http://127.0.0.1:8545 \ + --private-key +``` + +Broadcast on Anvil: + +```shell +anvil +forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url http://127.0.0.1:8545 \ + --broadcast \ + --private-key +``` diff --git a/docs/frontend.md b/docs/frontend.md new file mode 100644 index 00000000..7eb78d03 --- /dev/null +++ b/docs/frontend.md @@ -0,0 +1,15 @@ +# Web Frontend + +`frontend/` provides a lightweight UI for entering Safe + Optimistic Governor parameters and generating the same deployment flow as the script. It uses the connected wallet to submit transactions. + +## Setup + +```shell +cd frontend +npm install +npm run dev +``` + +## Environment Overrides + +The UI supports `MODULE_PROXY_FACTORY` (optionally with `VITE_` or `NEXT_PUBLIC_` prefixes). Other defaults are currently hardcoded in `frontend/src/App.jsx` and can be edited there or wired to env vars. diff --git a/docs/signers.md b/docs/signers.md new file mode 100644 index 00000000..b54791c8 --- /dev/null +++ b/docs/signers.md @@ -0,0 +1,57 @@ +# Signers and Key Management + +You can avoid storing raw private keys in `.env` by using the agent’s signer helper and injecting the key at runtime for Forge scripts. + +## Supported Signer Types + +- `env`: `PRIVATE_KEY` +- `keystore`: `KEYSTORE_PATH`, `KEYSTORE_PASSWORD` +- `keychain`: `KEYCHAIN_SERVICE`, `KEYCHAIN_ACCOUNT` +- `vault`: `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_SECRET_PATH`, optional `VAULT_SECRET_KEY` +- `kms` / `vault-signer` / `rpc`: `SIGNER_RPC_URL`, `SIGNER_ADDRESS` (RPC signer that accepts `eth_sendTransaction`) + +## Use With Forge Scripts + +The `agent/with-signer.mjs` helper resolves a signer and injects it as an env var (for example `DEPLOYER_PK`, `PROPOSER_PK`, `EXECUTOR_PK`) for any Forge script. + +```shell +# Private key from env +SIGNER_TYPE=env PRIVATE_KEY=0x... \ + node agent/with-signer.mjs --env DEPLOYER_PK -- \ + forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast + +# Encrypted keystore +SIGNER_TYPE=keystore KEYSTORE_PATH=./keys/deployer.json KEYSTORE_PASSWORD=... \ + node agent/with-signer.mjs --env DEPLOYER_PK -- \ + forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast + +# OS keychain +SIGNER_TYPE=keychain KEYCHAIN_SERVICE=og-deployer KEYCHAIN_ACCOUNT=deployer \ + node agent/with-signer.mjs --env DEPLOYER_PK -- \ + forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast + +# Vault KV (private key stored as a secret) +SIGNER_TYPE=vault VAULT_ADDR=https://vault.example.com VAULT_TOKEN=... VAULT_SECRET_PATH=secret/data/og-deployer \ + node agent/with-signer.mjs --env DEPLOYER_PK -- \ + forge script script/DeploySafeWithOptimisticGovernor.s.sol:DeploySafeWithOptimisticGovernor \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast +``` + +For interactions, swap the env var: + +```shell +SIGNER_TYPE=keystore KEYSTORE_PATH=./keys/proposer.json KEYSTORE_PASSWORD=... \ + node agent/with-signer.mjs --env PROPOSER_PK -- \ + forge script script/ProposeCommitmentTransfer.s.sol:ProposeCommitmentTransfer \ + --rpc-url $MAINNET_RPC_URL \ + --broadcast +``` + +Forge scripts still expect a private key env var. For KMS/Vault signing without exporting private keys, use an RPC signer proxy that supports `eth_sendTransaction` (set `SIGNER_RPC_URL` and `SIGNER_ADDRESS`). diff --git a/docs/templates/AGENTS.md.template b/docs/templates/AGENTS.md.template new file mode 100644 index 00000000..0ad53398 --- /dev/null +++ b/docs/templates/AGENTS.md.template @@ -0,0 +1,25 @@ +# Area Guidelines + +## Scope + +This file applies to: + +- `` +- `` + +## Purpose + +Describe what belongs in this area and how it fits the repository. + +## Rules + +- Keep changes local to this area unless they are clearly cross-cutting. +- Reference root `AGENTS.md` and `CONTRIBUTING.md` for global expectations. + +## Locality Rule + +If this area includes agent implementations, keep agent-specific functionality in local agent files instead of shared generalized files. + +## Validation + +List the minimum checks contributors should run for changes in this area. diff --git a/docs/templates/AGENT_README.md.template b/docs/templates/AGENT_README.md.template new file mode 100644 index 00000000..20ab7c92 --- /dev/null +++ b/docs/templates/AGENT_README.md.template @@ -0,0 +1,20 @@ +# + +## Purpose + +Describe the commitment or policy this agent is designed to serve. + +## Files + +- `agent.js`: agent-specific logic and decision flow. +- `commitment.txt`: plain-language commitment/rules. +- `agent.json`: metadata (if applicable). + +## Locality Rule + +Agent-specific behavior for this module must stay in this directory. Do not move single-agent logic into shared generalized files in `agent/src/lib/` or `agent/src/index.js`. + +## Validation + +- `` +- `` diff --git a/docs/templates/AREA_README.md.template b/docs/templates/AREA_README.md.template new file mode 100644 index 00000000..000e5353 --- /dev/null +++ b/docs/templates/AREA_README.md.template @@ -0,0 +1,25 @@ +# + +## What This Area Contains + +Short description of responsibility for this folder. + +## When To Edit This Area + +- Use for `` +- Avoid for `` + +## Extension Points + +- `` +- `` + +## Anti-Patterns + +- `` +- `` + +## How To Validate Changes + +- `` +- `` diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..fdd1ca78 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,10 @@ +# Testing and Common Commands + +```shell +forge build +forge test +forge fmt +forge snapshot +``` + +Add `-vv` for logs or `--mt ` to target specific tests. diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..9ad921bd --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,44 @@ +# Oya Commitments Frontend + +Lightweight web UI for configuring and deploying a commitment (Safe + Optimistic Governor module). It mirrors the Foundry deployment flow and helps craft the same onchain calls from a browser wallet. + +## Beta Disclaimer + +This is beta software provided “as is.” Use at your own risk. No guarantees of safety, correctness, or fitness for any purpose. + +## Prerequisites + +- Node.js 18+ +- npm (or pnpm/yarn) + +## Install & Run + +```shell +npm install +npm run dev +``` + +## Environment Variables + +The app reads env values directly or with `VITE_` / `NEXT_PUBLIC_` prefixes. + +Supported today: + +- `MODULE_PROXY_FACTORY` (optional; overrides the module proxy factory address) + +All other Safe / Optimistic Governor defaults are currently hardcoded in `src/App.jsx` (mainnet defaults). If you want to make those configurable, update the defaults or wire in additional env keys. + +## Build + +```shell +npm run build +npm run preview +``` + +## What It Does + +- Collects Safe + Optimistic Governor parameters (rules, collateral, bond, liveness, addresses). +- Deploys a Safe proxy and an Optimistic Governor module. +- Enables the module on the Safe. + +If you need the CLI-based flow instead, use the Foundry scripts in `script/` from the repo root. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..d42bca41 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + OG Deployer + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..cc8c2bcb --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,9834 @@ +{ + "name": "og-deployer-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "og-deployer-frontend", + "version": "0.1.0", + "dependencies": { + "@rainbow-me/rainbowkit": "^2.1.6", + "@tanstack/react-query": "^5.56.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.20.0", + "wagmi": "^2.12.7" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^7.3.1" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@base-org/account": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@base-org/account/-/account-2.4.0.tgz", + "integrity": "sha512-A4Umpi8B9/pqR78D1Yoze4xHyQaujioVRqqO3d6xuDFw9VRtjg6tK3bPlwE0aW+nVH/ntllCpPa2PbI8Rnjcug==", + "license": "Apache-2.0", + "dependencies": { + "@coinbase/cdp-sdk": "^1.0.0", + "@noble/hashes": "1.4.0", + "clsx": "1.2.1", + "eventemitter3": "5.0.1", + "idb-keyval": "6.2.1", + "ox": "0.6.9", + "preact": "10.24.2", + "viem": "^2.31.7", + "zustand": "5.0.3" + } + }, + "node_modules/@base-org/account/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@base-org/account/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@base-org/account/node_modules/ox": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", + "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@base-org/account/node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@coinbase/cdp-sdk": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@coinbase/cdp-sdk/-/cdp-sdk-1.43.0.tgz", + "integrity": "sha512-Fre1tvoIi4HAoC8/PgBoLsuZ9mt7K0R50EEC6i+6FaipW7oO3MABCx+vGAcM7EpcbVa7E6hTFe2/a0UdoajvYQ==", + "license": "MIT", + "dependencies": { + "@solana-program/system": "^0.10.0", + "@solana-program/token": "^0.9.0", + "@solana/kit": "^5.1.0", + "@solana/web3.js": "^1.98.1", + "abitype": "1.0.6", + "axios": "^1.12.2", + "axios-retry": "^4.5.0", + "jose": "^6.0.8", + "md5": "^2.3.0", + "uncrypto": "^0.1.3", + "viem": "^2.21.26", + "zod": "^3.24.4" + } + }, + "node_modules/@coinbase/cdp-sdk/node_modules/abitype": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.6.tgz", + "integrity": "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@coinbase/wallet-sdk": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.3.6.tgz", + "integrity": "sha512-4q8BNG1ViL4mSAAvPAtpwlOs1gpC+67eQtgIwNvT3xyeyFFd+guwkc8bcX5rTmQhXpqnhzC4f0obACbP9CqMSA==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "1.4.0", + "clsx": "1.2.1", + "eventemitter3": "5.0.1", + "idb-keyval": "6.2.1", + "ox": "0.6.9", + "preact": "10.24.2", + "viem": "^2.27.2", + "zustand": "5.0.3" + } + }, + "node_modules/@coinbase/wallet-sdk/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@coinbase/wallet-sdk/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@coinbase/wallet-sdk/node_modules/ox": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", + "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@coinbase/wallet-sdk/node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.5.tgz", + "integrity": "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==", + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@ethereumjs/common": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-3.2.0.tgz", + "integrity": "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==", + "license": "MIT", + "dependencies": { + "@ethereumjs/util": "^8.1.0", + "crc-32": "^1.2.0" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", + "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", + "license": "MPL-2.0", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-4.2.0.tgz", + "integrity": "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/common": "^3.2.0", + "@ethereumjs/rlp": "^4.0.1", + "@ethereumjs/util": "^8.1.0", + "ethereum-cryptography": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/util": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", + "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "ethereum-cryptography": "^2.0.0", + "micro-ftch": "^0.3.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@gemini-wallet/core": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@gemini-wallet/core/-/core-0.3.2.tgz", + "integrity": "sha512-Z4aHi3ECFf5oWYWM3F1rW83GJfB9OvhBYPTmb5q+VyK3uvzvS48lwo+jwh2eOoCRWEuT/crpb9Vwp2QaS5JqgQ==", + "license": "MIT", + "dependencies": { + "@metamask/rpc-errors": "7.0.2", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "viem": ">=2.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz", + "integrity": "sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA==", + "dependencies": { + "@metamask/json-rpc-engine": "^7.0.0", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/json-rpc-engine": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz", + "integrity": "sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg==", + "license": "ISC", + "dependencies": { + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/json-rpc-engine/node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/rpc-errors": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", + "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", + "license": "MIT", + "dependencies": { + "@metamask/utils": "^9.0.0", + "fast-safe-stringify": "^2.0.6" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", + "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.1.2", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "semver": "^7.3.8", + "superstruct": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/superstruct": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz", + "integrity": "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@metamask/json-rpc-engine": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz", + "integrity": "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA==", + "license": "ISC", + "dependencies": { + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-engine/node_modules/@metamask/rpc-errors": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", + "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", + "license": "MIT", + "dependencies": { + "@metamask/utils": "^9.0.0", + "fast-safe-stringify": "^2.0.6" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-engine/node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-engine/node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-engine/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/json-rpc-engine/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@metamask/json-rpc-middleware-stream": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz", + "integrity": "sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg==", + "license": "ISC", + "dependencies": { + "@metamask/json-rpc-engine": "^8.0.2", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0", + "readable-stream": "^3.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-middleware-stream/node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-middleware-stream/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/json-rpc-middleware-stream/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@metamask/object-multiplex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz", + "integrity": "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==", + "license": "ISC", + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.2" + }, + "engines": { + "node": "^16.20 || ^18.16 || >=20" + } + }, + "node_modules/@metamask/onboarding": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz", + "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==", + "license": "MIT", + "dependencies": { + "bowser": "^2.9.0" + } + }, + "node_modules/@metamask/providers": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-16.1.0.tgz", + "integrity": "sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g==", + "license": "MIT", + "dependencies": { + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/json-rpc-middleware-stream": "^7.0.1", + "@metamask/object-multiplex": "^2.0.0", + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.1.1", + "@metamask/utils": "^8.3.0", + "detect-browser": "^5.2.0", + "extension-port-stream": "^3.0.0", + "fast-deep-equal": "^3.1.3", + "is-stream": "^2.0.0", + "readable-stream": "^3.6.2", + "webextension-polyfill": "^0.10.0" + }, + "engines": { + "node": "^18.18 || >=20" + } + }, + "node_modules/@metamask/providers/node_modules/@metamask/rpc-errors": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", + "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", + "license": "MIT", + "dependencies": { + "@metamask/utils": "^9.0.0", + "fast-safe-stringify": "^2.0.6" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/providers/node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/providers/node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/providers/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/providers/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@metamask/rpc-errors": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-7.0.2.tgz", + "integrity": "sha512-YYYHsVYd46XwY2QZzpGeU4PSdRhHdxnzkB8piWGvJW2xbikZ3R+epAYEL4q/K8bh9JPTucsUdwRFnACor1aOYw==", + "license": "MIT", + "dependencies": { + "@metamask/utils": "^11.0.1", + "fast-safe-stringify": "^2.0.6" + }, + "engines": { + "node": "^18.20 || ^20.17 || >=22" + } + }, + "node_modules/@metamask/safe-event-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", + "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", + "license": "ISC", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@metamask/sdk": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.33.1.tgz", + "integrity": "sha512-1mcOQVGr9rSrVcbKPNVzbZ8eCl1K0FATsYH3WJ/MH4WcZDWGECWrXJPNMZoEAkLxWiMe8jOQBumg2pmcDa9zpQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@metamask/onboarding": "^1.0.1", + "@metamask/providers": "16.1.0", + "@metamask/sdk-analytics": "0.0.5", + "@metamask/sdk-communication-layer": "0.33.1", + "@metamask/sdk-install-modal-web": "0.32.1", + "@paulmillr/qr": "^0.2.1", + "bowser": "^2.9.0", + "cross-fetch": "^4.0.0", + "debug": "4.3.4", + "eciesjs": "^0.4.11", + "eth-rpc-errors": "^4.0.3", + "eventemitter2": "^6.4.9", + "obj-multiplex": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^3.6.2", + "socket.io-client": "^4.5.1", + "tslib": "^2.6.0", + "util": "^0.12.4", + "uuid": "^8.3.2" + } + }, + "node_modules/@metamask/sdk-analytics": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@metamask/sdk-analytics/-/sdk-analytics-0.0.5.tgz", + "integrity": "sha512-fDah+keS1RjSUlC8GmYXvx6Y26s3Ax1U9hGpWb6GSY5SAdmTSIqp2CvYy6yW0WgLhnYhW+6xERuD0eVqV63QIQ==", + "license": "MIT", + "dependencies": { + "openapi-fetch": "^0.13.5" + } + }, + "node_modules/@metamask/sdk-communication-layer": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.33.1.tgz", + "integrity": "sha512-0bI9hkysxcfbZ/lk0T2+aKVo1j0ynQVTuB3sJ5ssPWlz+Z3VwveCkP1O7EVu1tsVVCb0YV5WxK9zmURu2FIiaA==", + "dependencies": { + "@metamask/sdk-analytics": "0.0.5", + "bufferutil": "^4.0.8", + "date-fns": "^2.29.3", + "debug": "4.3.4", + "utf-8-validate": "^5.0.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "cross-fetch": "^4.0.0", + "eciesjs": "*", + "eventemitter2": "^6.4.9", + "readable-stream": "^3.6.2", + "socket.io-client": "^4.5.1" + } + }, + "node_modules/@metamask/sdk-communication-layer/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@metamask/sdk-communication-layer/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/@metamask/sdk-install-modal-web": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.32.1.tgz", + "integrity": "sha512-MGmAo6qSjf1tuYXhCu2EZLftq+DSt5Z7fsIKr2P+lDgdTPWgLfZB1tJKzNcwKKOdf6q9Qmmxn7lJuI/gq5LrKw==", + "dependencies": { + "@paulmillr/qr": "^0.2.1" + } + }, + "node_modules/@metamask/sdk/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@metamask/sdk/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/@metamask/superstruct": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.2.1.tgz", + "integrity": "sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/utils": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-11.9.0.tgz", + "integrity": "sha512-wRnoSDD9jTWOge/+reFviJQANhS+uy8Y+OEwRanp5mQeGTjBFmK1r2cTOnei2UCZRV1crXHzeJVSFEoDDcgRbA==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "@types/lodash": "^4.17.20", + "debug": "^4.3.4", + "lodash": "^4.17.21", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": "^18.18 || ^20.14 || >=22" + } + }, + "node_modules/@metamask/utils/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/utils/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paulmillr/qr": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@paulmillr/qr/-/qr-0.2.1.tgz", + "integrity": "sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==", + "deprecated": "The package is now available as \"qr\": npm install qr", + "license": "(MIT OR Apache-2.0)", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@rainbow-me/rainbowkit": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@rainbow-me/rainbowkit/-/rainbowkit-2.2.10.tgz", + "integrity": "sha512-8+E4die1A2ovN9t3lWxWnwqTGEdFqThXDQRj+E4eDKuUKyymYD+66Gzm6S9yfg8E95c6hmGlavGUfYPtl1EagA==", + "license": "MIT", + "dependencies": { + "@vanilla-extract/css": "1.17.3", + "@vanilla-extract/dynamic": "2.1.4", + "@vanilla-extract/sprinkles": "1.6.4", + "clsx": "2.1.1", + "cuer": "0.0.3", + "react-remove-scroll": "2.6.2", + "ua-parser-js": "^1.0.37" + }, + "engines": { + "node": ">=12.4" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "react-dom": ">=18", + "viem": "2.x", + "wagmi": "^2.9.0" + } + }, + "node_modules/@reown/appkit": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.8.tgz", + "integrity": "sha512-51kTleozhA618T1UvMghkhKfaPcc9JlKwLJ5uV+riHyvSoWPKPRIa5A6M1Wano5puNyW0s3fwywhyqTHSilkaA==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-controllers": "1.7.8", + "@reown/appkit-pay": "1.7.8", + "@reown/appkit-polyfills": "1.7.8", + "@reown/appkit-scaffold-ui": "1.7.8", + "@reown/appkit-ui": "1.7.8", + "@reown/appkit-utils": "1.7.8", + "@reown/appkit-wallet": "1.7.8", + "@walletconnect/types": "2.21.0", + "@walletconnect/universal-provider": "2.21.0", + "bs58": "6.0.0", + "valtio": "1.13.2", + "viem": ">=2.29.0" + } + }, + "node_modules/@reown/appkit-common": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-common/-/appkit-common-1.7.8.tgz", + "integrity": "sha512-ridIhc/x6JOp7KbDdwGKY4zwf8/iK8EYBl+HtWrruutSLwZyVi5P8WaZa+8iajL6LcDcDF7LoyLwMTym7SRuwQ==", + "license": "Apache-2.0", + "dependencies": { + "big.js": "6.2.2", + "dayjs": "1.11.13", + "viem": ">=2.29.0" + } + }, + "node_modules/@reown/appkit-controllers": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-controllers/-/appkit-controllers-1.7.8.tgz", + "integrity": "sha512-IdXlJlivrlj6m63VsGLsjtPHHsTWvKGVzWIP1fXZHVqmK+rZCBDjCi9j267Rb9/nYRGHWBtlFQhO8dK35WfeDA==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-wallet": "1.7.8", + "@walletconnect/universal-provider": "2.21.0", + "valtio": "1.13.2", + "viem": ">=2.29.0" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/core": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.0.tgz", + "integrity": "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.33.0", + "events": "3.3.0", + "uint8arrays": "3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/sign-client": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.0.tgz", + "integrity": "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/core": "2.21.0", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/types": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.0.tgz", + "integrity": "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/universal-provider": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.0.tgz", + "integrity": "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.21.0", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "es-toolkit": "1.33.0", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/utils": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.0.tgz", + "integrity": "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "1.2.1", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "bs58": "6.0.0", + "detect-browser": "5.3.0", + "query-string": "7.1.3", + "uint8arrays": "3.1.0", + "viem": "2.23.2" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/utils/node_modules/viem": { + "version": "2.23.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", + "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.7", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-controllers/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-controllers/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/@reown/appkit-controllers/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/@reown/appkit-controllers/node_modules/ox": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", + "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-controllers/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-controllers/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@reown/appkit-pay": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-pay/-/appkit-pay-1.7.8.tgz", + "integrity": "sha512-OSGQ+QJkXx0FEEjlpQqIhT8zGJKOoHzVnyy/0QFrl3WrQTjCzg0L6+i91Ad5Iy1zb6V5JjqtfIFpRVRWN4M3pw==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-controllers": "1.7.8", + "@reown/appkit-ui": "1.7.8", + "@reown/appkit-utils": "1.7.8", + "lit": "3.3.0", + "valtio": "1.13.2" + } + }, + "node_modules/@reown/appkit-polyfills": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-polyfills/-/appkit-polyfills-1.7.8.tgz", + "integrity": "sha512-W/kq786dcHHAuJ3IV2prRLEgD/2iOey4ueMHf1sIFjhhCGMynMkhsOhQMUH0tzodPqUgAC494z4bpIDYjwWXaA==", + "license": "Apache-2.0", + "dependencies": { + "buffer": "6.0.3" + } + }, + "node_modules/@reown/appkit-scaffold-ui": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-scaffold-ui/-/appkit-scaffold-ui-1.7.8.tgz", + "integrity": "sha512-RCeHhAwOrIgcvHwYlNWMcIDibdI91waaoEYBGw71inE0kDB8uZbE7tE6DAXJmDkvl0qPh+DqlC4QbJLF1FVYdQ==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-controllers": "1.7.8", + "@reown/appkit-ui": "1.7.8", + "@reown/appkit-utils": "1.7.8", + "@reown/appkit-wallet": "1.7.8", + "lit": "3.3.0" + } + }, + "node_modules/@reown/appkit-ui": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-ui/-/appkit-ui-1.7.8.tgz", + "integrity": "sha512-1hjCKjf6FLMFzrulhl0Y9Vb9Fu4royE+SXCPSWh4VhZhWqlzUFc7kutnZKx8XZFVQH4pbBvY62SpRC93gqoHow==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-controllers": "1.7.8", + "@reown/appkit-wallet": "1.7.8", + "lit": "3.3.0", + "qrcode": "1.5.3" + } + }, + "node_modules/@reown/appkit-utils": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-utils/-/appkit-utils-1.7.8.tgz", + "integrity": "sha512-8X7UvmE8GiaoitCwNoB86pttHgQtzy4ryHZM9kQpvjQ0ULpiER44t1qpVLXNM4X35O0v18W0Dk60DnYRMH2WRw==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-controllers": "1.7.8", + "@reown/appkit-polyfills": "1.7.8", + "@reown/appkit-wallet": "1.7.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/universal-provider": "2.21.0", + "valtio": "1.13.2", + "viem": ">=2.29.0" + }, + "peerDependencies": { + "valtio": "1.13.2" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/core": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.0.tgz", + "integrity": "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.33.0", + "events": "3.3.0", + "uint8arrays": "3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/sign-client": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.0.tgz", + "integrity": "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/core": "2.21.0", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/types": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.0.tgz", + "integrity": "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/universal-provider": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.0.tgz", + "integrity": "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.21.0", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "es-toolkit": "1.33.0", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/utils": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.0.tgz", + "integrity": "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "1.2.1", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "bs58": "6.0.0", + "detect-browser": "5.3.0", + "query-string": "7.1.3", + "uint8arrays": "3.1.0", + "viem": "2.23.2" + } + }, + "node_modules/@reown/appkit-utils/node_modules/@walletconnect/utils/node_modules/viem": { + "version": "2.23.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", + "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.7", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-utils/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-utils/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/@reown/appkit-utils/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/@reown/appkit-utils/node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/@reown/appkit-utils/node_modules/ox": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", + "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-utils/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@reown/appkit-utils/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@reown/appkit-wallet": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.8.tgz", + "integrity": "sha512-kspz32EwHIOT/eg/ZQbFPxgXq0B/olDOj3YMu7gvLEFz4xyOFd/wgzxxAXkp5LbG4Cp++s/elh79rVNmVFdB9A==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.8", + "@reown/appkit-polyfills": "1.7.8", + "@walletconnect/logger": "2.1.2", + "zod": "3.22.4" + } + }, + "node_modules/@reown/appkit-wallet/node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@reown/appkit/node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit/node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit/node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/core": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.0.tgz", + "integrity": "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.33.0", + "events": "3.3.0", + "uint8arrays": "3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/sign-client": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.0.tgz", + "integrity": "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/core": "2.21.0", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/types": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.0.tgz", + "integrity": "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/universal-provider": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.0.tgz", + "integrity": "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.21.0", + "@walletconnect/types": "2.21.0", + "@walletconnect/utils": "2.21.0", + "es-toolkit": "1.33.0", + "events": "3.3.0" + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/utils": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.0.tgz", + "integrity": "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "1.2.1", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.0", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "bs58": "6.0.0", + "detect-browser": "5.3.0", + "query-string": "7.1.3", + "uint8arrays": "3.1.0", + "viem": "2.23.2" + } + }, + "node_modules/@reown/appkit/node_modules/@walletconnect/utils/node_modules/viem": { + "version": "2.23.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", + "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.7", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@reown/appkit/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@reown/appkit/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/@reown/appkit/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/@reown/appkit/node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/@reown/appkit/node_modules/ox": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", + "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@reown/appkit/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@reown/appkit/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@safe-global/safe-apps-provider": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.6.tgz", + "integrity": "sha512-4LhMmjPWlIO8TTDC2AwLk44XKXaK6hfBTWyljDm0HQ6TWlOEijVWNrt2s3OCVMSxlXAcEzYfqyu1daHZooTC2Q==", + "license": "MIT", + "dependencies": { + "@safe-global/safe-apps-sdk": "^9.1.0", + "events": "^3.3.0" + } + }, + "node_modules/@safe-global/safe-apps-sdk": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-sdk/-/safe-apps-sdk-9.1.0.tgz", + "integrity": "sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q==", + "license": "MIT", + "dependencies": { + "@safe-global/safe-gateway-typescript-sdk": "^3.5.3", + "viem": "^2.1.1" + } + }, + "node_modules/@safe-global/safe-gateway-typescript-sdk": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.23.1.tgz", + "integrity": "sha512-6ORQfwtEJYpalCeVO21L4XXGSdbEMfyp2hEv6cP82afKXSwvse6d3sdelgaPWUxHIsFRkWvHDdzh8IyyKHZKxw==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@solana-program/system": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@solana-program/system/-/system-0.10.0.tgz", + "integrity": "sha512-Go+LOEZmqmNlfr+Gjy5ZWAdY5HbYzk2RBewD9QinEU/bBSzpFfzqDRT55JjFRBGJUvMgf3C2vfXEGT4i8DSI4g==", + "license": "Apache-2.0", + "peerDependencies": { + "@solana/kit": "^5.0" + } + }, + "node_modules/@solana-program/token": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@solana-program/token/-/token-0.9.0.tgz", + "integrity": "sha512-vnZxndd4ED4Fc56sw93cWZ2djEeeOFxtaPS8SPf5+a+JZjKA/EnKqzbE1y04FuMhIVrLERQ8uR8H2h72eZzlsA==", + "license": "Apache-2.0", + "peerDependencies": { + "@solana/kit": "^5.0" + } + }, + "node_modules/@solana/accounts": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-5.4.0.tgz", + "integrity": "sha512-qHtAtwCcCFTXcya6JOOG1nzYicivivN/JkcYNHr10qOp9b4MVRkfW1ZAAG1CNzjMe5+mwtEl60RwdsY9jXNb+Q==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/rpc-spec": "5.4.0", + "@solana/rpc-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/addresses": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-5.4.0.tgz", + "integrity": "sha512-YRHiH30S8qDV4bZ+mtEk589PGfBuXHzD/fK2Z+YI5f/+s+yi/5le/fVw7PN6LxnnmVQKiRCDUiNF+WmFFKi6QQ==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/nominal-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/assertions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-5.4.0.tgz", + "integrity": "sha512-8EP7mkdnrPc9y67FqWeAPzdWq2qAOkxsuo+ZBIXNWtIixDtXIdHrgjZ/wqbWxLgSTtXEfBCjpZU55Xw2Qfbwyg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "license": "MIT", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/codecs": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-5.4.0.tgz", + "integrity": "sha512-IbDCUvNX0MrkQahxiXj9rHzkd/fYfp1F2nTJkHGH8v+vPfD+YPjl007ZBM38EnCeXj/Xn+hxqBBivPvIHP29dA==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.4.0", + "@solana/codecs-data-structures": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/options": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.4.0.tgz", + "integrity": "sha512-rQ5jXgiDe2vIU+mYCHDjgwMd9WdzZfh4sc5H6JgYleAUjeTUX6mx8hTV2+pcXvvn27LPrgrt9jfxswbDb8O8ww==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-data-structures": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-5.4.0.tgz", + "integrity": "sha512-LVssbdQ1GfY6upnxW3mufYsNfvTWKnHNk5Hx2gHuOYJhm3HZlp+Y8zvuoY65G1d1xAXkPz5YVGxaSeVIRWLGWg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.4.0.tgz", + "integrity": "sha512-z6LMkY+kXWx1alrvIDSAxexY5QLhsso638CjM7XI1u6dB7drTLWKhifyjnm1vOQc1VPVFmbYxTgKKpds8TY8tg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.4.0", + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-strings": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-5.4.0.tgz", + "integrity": "sha512-w0trrjfQDhkCVz7O1GTmHBk9m+MkljKx2uNBbQAD3/yW2Qn9dYiTrZ1/jDVq0/+lPPAUkbT3s3Yo7HUZ2QFmHw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/errors": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.4.0.tgz", + "integrity": "sha512-hNoAOmlZAszaVBrAy1Jf7amHJ8wnUnTU0BqhNQXknbSvirvsYr81yEud2iq18YiCqhyJ9SuQ5kWrSAT0x7S0oA==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/fast-stable-stringify": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-5.4.0.tgz", + "integrity": "sha512-KB7PUL7yalPvbWCezzyUDVRDp39eHLPH7OJ6S8VFT8YNIFUANwwj5ctui50Fim76kvSYDdYJOclXV45O2gfQ8Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/functional": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-5.4.0.tgz", + "integrity": "sha512-32ghHO0bg6GgX/7++0/7Lps6RgeXD2gKF1okiuyEGuVfKENIapgaQdcGhUwb3q6D6fv6MRAVn/Yve4jopGVNMQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/instruction-plans": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/instruction-plans/-/instruction-plans-5.4.0.tgz", + "integrity": "sha512-5xbJ+I/pP2aWECmK75bEM1zCnIITlohAK83dVN+t5X2vBFrr6M9gifo8r4Opdnibsgo6QVVkKPxRo5zow5j0ig==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/instructions": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/promises": "5.4.0", + "@solana/transaction-messages": "5.4.0", + "@solana/transactions": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/instructions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-5.4.0.tgz", + "integrity": "sha512-//a7jpHbNoAgTqy3YyqG1X6QhItJLKzJa6zuYJGCwaAAJye7BxS9pxJBgb2mUt7CGidhUksf+U8pmLlxCNWYyg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.4.0", + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/keys": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-5.4.0.tgz", + "integrity": "sha512-zQVbAwdoXorgXjlhlVTZaymFG6N8n1zn2NT+xI6S8HtbrKIB/42xPdXFh+zIihGzRw+9k8jzU7Axki/IPm6qWQ==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/nominal-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/kit": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.4.0.tgz", + "integrity": "sha512-aVjN26jOEzJA6UBYxSTQciZPXgTxWnO/WysHrw+yeBL/5AaTZnXEgb4j5xV6cUFzOlVxhJBrx51xtoxSqJ0u3g==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "5.4.0", + "@solana/addresses": "5.4.0", + "@solana/codecs": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/instruction-plans": "5.4.0", + "@solana/instructions": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/offchain-messages": "5.4.0", + "@solana/plugin-core": "5.4.0", + "@solana/programs": "5.4.0", + "@solana/rpc": "5.4.0", + "@solana/rpc-api": "5.4.0", + "@solana/rpc-parsed-types": "5.4.0", + "@solana/rpc-spec-types": "5.4.0", + "@solana/rpc-subscriptions": "5.4.0", + "@solana/rpc-types": "5.4.0", + "@solana/signers": "5.4.0", + "@solana/sysvars": "5.4.0", + "@solana/transaction-confirmation": "5.4.0", + "@solana/transaction-messages": "5.4.0", + "@solana/transactions": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/nominal-types": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-5.4.0.tgz", + "integrity": "sha512-h4dTRQwTerzksE5B1WmObN6TvLo8dYUd7kpUUynGd8WJjK0zz3zkDhq0MkA3aF6A1C2C82BSGqSsN9EN0E6Exg==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/offchain-messages": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/offchain-messages/-/offchain-messages-5.4.0.tgz", + "integrity": "sha512-DjdlYJCcKfgh4dkdk+owH1bP+Q4BRqCs55mgWWp9PTwm/HHy/a5vcMtCi1GyIQXfhtNNvKBLbXrUE0Fxej8qlg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-data-structures": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/nominal-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/options": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-5.4.0.tgz", + "integrity": "sha512-h4vTWRChEXPhaHo9i1pCyQBWWs+NqYPQRXSAApqpUYvHb9Kct/C6KbHjfyaRMyqNQnDHLcJCX7oW9tk0iRDzIg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.4.0", + "@solana/codecs-data-structures": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/plugin-core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/plugin-core/-/plugin-core-5.4.0.tgz", + "integrity": "sha512-e1aLGLldW7C5113qTOjFYSGq95a4QC9TWb77iq+8l6h085DcNj+195r4E2zKaINrevQjQTwvxo00oUyHP7hSJA==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/programs": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-5.4.0.tgz", + "integrity": "sha512-Sc90WK9ZZ7MghOflIvkrIm08JwsFC99yqSJy28/K+hDP2tcx+1x+H6OFP9cumW9eUA1+JVRDeKAhA8ak7e/kUA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/promises": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-5.4.0.tgz", + "integrity": "sha512-23mfgNBbuP6Q+4vsixGy+GkyZ7wBLrxTBNXqrG/XWrJhjuuSkjEUGaK4Fx5o7LIrBi6KGqPknKxmTlvqnJhy2Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-5.4.0.tgz", + "integrity": "sha512-S6GRG+usnubDs0JSpgc0ZWEh9IPL5KPWMuBoD8ggGVOIVWntp53FpvhYslNzbxWBXlTvJecr2todBipGVM/AqQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/fast-stable-stringify": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/rpc-api": "5.4.0", + "@solana/rpc-spec": "5.4.0", + "@solana/rpc-spec-types": "5.4.0", + "@solana/rpc-transformers": "5.4.0", + "@solana/rpc-transport-http": "5.4.0", + "@solana/rpc-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-api": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-5.4.0.tgz", + "integrity": "sha512-FJL6KaAsQ4DhfhLKKMcqbTpToNFwHlABCemIpOunE3OSqJFDrmc/NbsEaLIoeHyIg3d1Imo49GIUOn2TEouFUA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/rpc-parsed-types": "5.4.0", + "@solana/rpc-spec": "5.4.0", + "@solana/rpc-transformers": "5.4.0", + "@solana/rpc-types": "5.4.0", + "@solana/transaction-messages": "5.4.0", + "@solana/transactions": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-parsed-types": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-5.4.0.tgz", + "integrity": "sha512-IRQuSzx+Sj1A3XGiIzguNZlMjMMybXTTjV/RnTwBgnJQPd/H4us4pfPD94r+/yolWDVfGjJRm04hnKVMjJU8Rg==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-spec": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-5.4.0.tgz", + "integrity": "sha512-XMhxBb1GuZ3Kaeu5WNHB5KteCQ/aVuMByZmUKPqaanD+gs5MQZr0g62CvN7iwRlFU7GC18Q73ROWR3/JjzbXTA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/rpc-spec-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-spec-types": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-5.4.0.tgz", + "integrity": "sha512-JU9hC5/iyJx30ym17gpoXDtT9rCbO6hLpB6UDhSFFoNeirxtTVb4OdnKtsjJDfXAiXsynJRsZRwfj3vGxRLgQw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-5.4.0.tgz", + "integrity": "sha512-051t1CEjjAzM9ohjj2zb3ED70yeS3ZY8J5wSytL6tthTGImw/JB2a0D9DWMOKriFKt496n95IC+IdpJ35CpBWA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/fast-stable-stringify": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/promises": "5.4.0", + "@solana/rpc-spec-types": "5.4.0", + "@solana/rpc-subscriptions-api": "5.4.0", + "@solana/rpc-subscriptions-channel-websocket": "5.4.0", + "@solana/rpc-subscriptions-spec": "5.4.0", + "@solana/rpc-transformers": "5.4.0", + "@solana/rpc-types": "5.4.0", + "@solana/subscribable": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-api": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-5.4.0.tgz", + "integrity": "sha512-euAFIG6ruEsqK+MsrL1tGSMbbOumm8UAyGzlD/kmXsAqqhcVsSeZdv5+BMIHIBsQ93GHcloA8UYw1BTPhpgl9w==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/rpc-subscriptions-spec": "5.4.0", + "@solana/rpc-transformers": "5.4.0", + "@solana/rpc-types": "5.4.0", + "@solana/transaction-messages": "5.4.0", + "@solana/transactions": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-5.4.0.tgz", + "integrity": "sha512-kWCmlW65MccxqXwKsIz+LkXUYQizgvBrrgYOkyclJHPa+zx4gqJjam87+wzvO9cfbDZRer3wtJBaRm61gTHNbw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/rpc-subscriptions-spec": "5.4.0", + "@solana/subscribable": "5.4.0", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@solana/rpc-subscriptions-spec": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-5.4.0.tgz", + "integrity": "sha512-ELaV9Z39GtKyUO0++he00ymWleb07QXYJhSfA0e1N5Q9hXu/Y366kgXHDcbZ/oUJkT3ylNgTupkrsdtiy8Ryow==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/promises": "5.4.0", + "@solana/rpc-spec-types": "5.4.0", + "@solana/subscribable": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-transformers": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-5.4.0.tgz", + "integrity": "sha512-dZ8keYloLW+eRAwAPb471uWCFs58yHloLoI+QH0FulYpsSJ7F2BNWYcdnjSS/WiggsNcU6DhpWzYAzlEY66lGQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/nominal-types": "5.4.0", + "@solana/rpc-spec-types": "5.4.0", + "@solana/rpc-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-transport-http": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-5.4.0.tgz", + "integrity": "sha512-vidA+Qtqrnqp3QSVumWHdWJ/986yCr5+qX3fbc9KPm9Ofoto88OMWB/oLJvi2Tfges1UBu/jl+lJdsVckCM1bA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0", + "@solana/rpc-spec": "5.4.0", + "@solana/rpc-spec-types": "5.4.0", + "undici-types": "^7.18.2" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-types": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-5.4.0.tgz", + "integrity": "sha512-+C4N4/5AYzBdt3Y2yzkScknScy/jTx6wfvuJIY9XjOXtdDyZ8TmrnMwdPMTZPGLdLuHplJwlwy1acu/4hqmrBQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/nominal-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/signers": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-5.4.0.tgz", + "integrity": "sha512-s+fZxpi6UPr6XNk2pH/R84WjNRoSktrgG8AGNfsj/V8MJ++eKX7hhIf4JsHZtnnQXXrHmS3ozB2oHlc8yEJvCQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/instructions": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/nominal-types": "5.4.0", + "@solana/offchain-messages": "5.4.0", + "@solana/transaction-messages": "5.4.0", + "@solana/transactions": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/subscribable": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-5.4.0.tgz", + "integrity": "sha512-72LmfNX7UENgA24sn/xjlWpPAOsrxkWb9DQhuPZxly/gq8rl/rvr7Xu9qBkvFF2po9XpdUrKlccqY4awvfpltA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/sysvars": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.4.0.tgz", + "integrity": "sha512-A5NES7sOlFmpnsiEts5vgyL3NXrt/tGGVSEjlEGvsgwl5EDZNv+xWnNA400uMDqd9O3a5PmH7p/6NsgR+kUzSg==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "5.4.0", + "@solana/codecs": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/rpc-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transaction-confirmation": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-5.4.0.tgz", + "integrity": "sha512-EdSDgxs84/4gkjQw2r7N+Kgus8x9U+NFo0ufVG+48V8Hzy2t0rlBuXgIxwx0zZwUuTIgaKhpIutJgVncwZ5koA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/promises": "5.4.0", + "@solana/rpc": "5.4.0", + "@solana/rpc-subscriptions": "5.4.0", + "@solana/rpc-types": "5.4.0", + "@solana/transaction-messages": "5.4.0", + "@solana/transactions": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transaction-messages": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-5.4.0.tgz", + "integrity": "sha512-qd/3kZDaPiHM0amhn3vXnupfcsFTVz6CYuHXvq9HFv/fq32+5Kp1FMLnmHwoSxQxdTMDghPdOhC4vhNhuWmuVQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-data-structures": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/instructions": "5.4.0", + "@solana/nominal-types": "5.4.0", + "@solana/rpc-types": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transactions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-5.4.0.tgz", + "integrity": "sha512-OuY4M4x/xna8KZQIrz8tSrI9EEul9Od97XejqFmGGkEjbRsUOfJW8705TveTW8jU3bd5RGecFYscPgS2F+m7jQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.4.0", + "@solana/codecs-core": "5.4.0", + "@solana/codecs-data-structures": "5.4.0", + "@solana/codecs-numbers": "5.4.0", + "@solana/codecs-strings": "5.4.0", + "@solana/errors": "5.4.0", + "@solana/functional": "5.4.0", + "@solana/instructions": "5.4.0", + "@solana/keys": "5.4.0", + "@solana/nominal-types": "5.4.0", + "@solana/rpc-types": "5.4.0", + "@solana/transaction-messages": "5.4.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.17", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.17.tgz", + "integrity": "sha512-hDww+RyyYhjhUfoYQ4es6pbgxY7LNiPWxt4l1nJqhByjndxJ7HIjDxTBtfvMr5HwjYavMrd+ids5g4Rfev3lVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.17", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.17.tgz", + "integrity": "sha512-PGc2u9KLwohDUSchjW9MZqeDQJfJDON7y4W7REdNBgiFKxQy+Pf7eGjiFWEj5xPqKzAeHYdAb62IWI1a9UJyGQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.17" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", + "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vanilla-extract/css": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.17.3.tgz", + "integrity": "sha512-jHivr1UPoJTX5Uel4AZSOwrCf4mO42LcdmnhJtUxZaRWhW4FviFbIfs0moAWWld7GOT+2XnuVZjjA/K32uUnMQ==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.0", + "@vanilla-extract/private": "^1.0.8", + "css-what": "^6.1.0", + "cssesc": "^3.0.0", + "csstype": "^3.0.7", + "dedent": "^1.5.3", + "deep-object-diff": "^1.1.9", + "deepmerge": "^4.2.2", + "lru-cache": "^10.4.3", + "media-query-parser": "^2.0.2", + "modern-ahocorasick": "^1.0.0", + "picocolors": "^1.0.0" + } + }, + "node_modules/@vanilla-extract/dynamic": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vanilla-extract/dynamic/-/dynamic-2.1.4.tgz", + "integrity": "sha512-7+Ot7VlP3cIzhJnTsY/kBtNs21s0YD7WI1rKJJKYP56BkbDxi/wrQUWMGEczKPUDkJuFcvbye+E2ub1u/mHH9w==", + "license": "MIT", + "dependencies": { + "@vanilla-extract/private": "^1.0.8" + } + }, + "node_modules/@vanilla-extract/private": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.9.tgz", + "integrity": "sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==", + "license": "MIT" + }, + "node_modules/@vanilla-extract/sprinkles": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@vanilla-extract/sprinkles/-/sprinkles-1.6.4.tgz", + "integrity": "sha512-lW3MuIcdIeHKX81DzhTnw68YJdL1ial05exiuvTLJMdHXQLKcVB93AncLPajMM6mUhaVVx5ALZzNHMTrq/U9Hg==", + "license": "MIT", + "peerDependencies": { + "@vanilla-extract/css": "^1.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@wagmi/connectors": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-6.2.0.tgz", + "integrity": "sha512-2NfkbqhNWdjfibb4abRMrn7u6rPjEGolMfApXss6HCDVt9AW2oVC6k8Q5FouzpJezElxLJSagWz9FW1zaRlanA==", + "license": "MIT", + "dependencies": { + "@base-org/account": "2.4.0", + "@coinbase/wallet-sdk": "4.3.6", + "@gemini-wallet/core": "0.3.2", + "@metamask/sdk": "0.33.1", + "@safe-global/safe-apps-provider": "0.18.6", + "@safe-global/safe-apps-sdk": "9.1.0", + "@walletconnect/ethereum-provider": "2.21.1", + "cbw-sdk": "npm:@coinbase/wallet-sdk@3.9.3", + "porto": "0.2.35" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@wagmi/core": "2.22.1", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/connectors/node_modules/ox": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.17.tgz", + "integrity": "sha512-rKAnhzhRU3Xh3hiko+i1ZxywZ55eWQzeS/Q4HRKLx2PqfHOolisZHErSsJVipGlmQKHW5qwOED/GighEw9dbLg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/connectors/node_modules/porto": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/porto/-/porto-0.2.35.tgz", + "integrity": "sha512-gu9FfjjvvYBgQXUHWTp6n3wkTxVtEcqFotM7i3GEZeoQbvLGbssAicCz6hFZ8+xggrJWwi/RLmbwNra50SMmUQ==", + "license": "MIT", + "dependencies": { + "hono": "^4.10.3", + "idb-keyval": "^6.2.1", + "mipd": "^0.0.7", + "ox": "^0.9.6", + "zod": "^4.1.5", + "zustand": "^5.0.1" + }, + "bin": { + "porto": "dist/cli/bin/index.js" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.59.0", + "@wagmi/core": ">=2.16.3", + "expo-auth-session": ">=7.0.8", + "expo-crypto": ">=15.0.7", + "expo-web-browser": ">=15.0.8", + "react": ">=18", + "react-native": ">=0.81.4", + "typescript": ">=5.4.0", + "viem": ">=2.37.0", + "wagmi": ">=2.0.0" + }, + "peerDependenciesMeta": { + "@tanstack/react-query": { + "optional": true + }, + "expo-auth-session": { + "optional": true + }, + "expo-crypto": { + "optional": true + }, + "expo-web-browser": { + "optional": true + }, + "react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + }, + "wagmi": { + "optional": true + } + } + }, + "node_modules/@wagmi/connectors/node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@wagmi/core": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz", + "integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==", + "license": "MIT", + "dependencies": { + "eventemitter3": "5.0.1", + "mipd": "0.0.7", + "zustand": "5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.0.0", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@tanstack/query-core": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/core/node_modules/zustand": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", + "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, + "node_modules/@walletconnect/core": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.1.tgz", + "integrity": "sha512-Tp4MHJYcdWD846PH//2r+Mu4wz1/ZU/fr9av1UWFiaYQ2t2TPLDiZxjLw54AAEpMqlEHemwCgiRiAmjR1NDdTQ==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.1", + "@walletconnect/utils": "2.21.1", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.33.0", + "events": "3.3.0", + "uint8arrays": "3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@walletconnect/core/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/core/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/environment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.1.tgz", + "integrity": "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/environment/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/ethereum-provider": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.21.1.tgz", + "integrity": "sha512-SSlIG6QEVxClgl1s0LMk4xr2wg4eT3Zn/Hb81IocyqNSGfXpjtawWxKxiC5/9Z95f1INyBD6MctJbL/R1oBwIw==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit": "1.7.8", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/sign-client": "2.21.1", + "@walletconnect/types": "2.21.1", + "@walletconnect/universal-provider": "2.21.1", + "@walletconnect/utils": "2.21.1", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/ethereum-provider/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/ethereum-provider/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/events": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", + "integrity": "sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==", + "license": "MIT", + "dependencies": { + "keyvaluestorage-interface": "^1.0.0", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/events/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/heartbeat": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@walletconnect/heartbeat/-/heartbeat-1.2.2.tgz", + "integrity": "sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw==", + "license": "MIT", + "dependencies": { + "@walletconnect/events": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-http-connection": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.8.tgz", + "integrity": "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.1", + "cross-fetch": "^3.1.4", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-http-connection/node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/@walletconnect/jsonrpc-provider": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", + "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.8", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-types": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz", + "integrity": "sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ==", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "keyvaluestorage-interface": "^1.0.0" + } + }, + "node_modules/@walletconnect/jsonrpc-utils": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz", + "integrity": "sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==", + "license": "MIT", + "dependencies": { + "@walletconnect/environment": "^1.0.1", + "@walletconnect/jsonrpc-types": "^1.0.3", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/jsonrpc-utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/jsonrpc-ws-connection": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.16.tgz", + "integrity": "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0", + "ws": "^7.5.1" + } + }, + "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@walletconnect/logger": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@walletconnect/logger/-/logger-2.1.2.tgz", + "integrity": "sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.2", + "pino": "7.11.0" + } + }, + "node_modules/@walletconnect/relay-api": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", + "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-types": "^1.0.2" + } + }, + "node_modules/@walletconnect/relay-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", + "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.0", + "@noble/hashes": "1.7.0", + "@walletconnect/safe-json": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/@walletconnect/relay-auth/node_modules/@noble/curves": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", + "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/relay-auth/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/safe-json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.2.tgz", + "integrity": "sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/safe-json/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/sign-client": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.1.tgz", + "integrity": "sha512-QaXzmPsMnKGV6tc4UcdnQVNOz4zyXgarvdIQibJ4L3EmLat73r5ZVl4c0cCOcoaV7rgM9Wbphgu5E/7jNcd3Zg==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/core": "2.21.1", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.1", + "@walletconnect/utils": "2.21.1", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/time/-/time-1.0.2.tgz", + "integrity": "sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/time/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/types": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.1.tgz", + "integrity": "sha512-UeefNadqP6IyfwWC1Yi7ux+ljbP2R66PLfDrDm8izmvlPmYlqRerJWJvYO4t0Vvr9wrG4Ko7E0c4M7FaPKT/sQ==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/types/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/types/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/universal-provider": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.1.tgz", + "integrity": "sha512-Wjx9G8gUHVMnYfxtasC9poGm8QMiPCpXpbbLFT+iPoQskDDly8BwueWnqKs4Mx2SdIAWAwuXeZ5ojk5qQOxJJg==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.21.1", + "@walletconnect/types": "2.21.1", + "@walletconnect/utils": "2.21.1", + "es-toolkit": "1.33.0", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.1.tgz", + "integrity": "sha512-VPZvTcrNQCkbGOjFRbC24mm/pzbRMUq2DSQoiHlhh0X1U7ZhuIrzVtAoKsrzu6rqjz0EEtGxCr3K1TGRqDG4NA==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "1.2.1", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.1", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "bs58": "6.0.0", + "detect-browser": "5.3.0", + "query-string": "7.1.3", + "uint8arrays": "3.1.0", + "viem": "2.23.2" + } + }, + "node_modules/@walletconnect/utils/node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/@walletconnect/utils/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/@walletconnect/utils/node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/@walletconnect/utils/node_modules/ox": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", + "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/viem": { + "version": "2.23.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", + "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.7", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@walletconnect/window-getters": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.1.tgz", + "integrity": "sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-getters/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/window-metadata": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz", + "integrity": "sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==", + "license": "MIT", + "dependencies": { + "@walletconnect/window-getters": "^1.0.1", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-metadata/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/async-mutex": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", + "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cbw-sdk": { + "name": "@coinbase/wallet-sdk", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-3.9.3.tgz", + "integrity": "sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.1", + "buffer": "^6.0.3", + "clsx": "^1.2.1", + "eth-block-tracker": "^7.1.0", + "eth-json-rpc-filters": "^6.0.0", + "eventemitter3": "^5.0.1", + "keccak": "^3.0.3", + "preact": "^10.16.0", + "sha.js": "^2.4.11" + } + }, + "node_modules/cbw-sdk/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/cuer": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/cuer/-/cuer-0.0.3.tgz", + "integrity": "sha512-f/UNxRMRCYtfLEGECAViByA3JNflZImOk11G9hwSd+44jvzrc99J35u5l+fbdQ2+ZG441GvOpaeGYBmWquZsbQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "qr": "~0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/derive-valtio": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", + "integrity": "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==", + "license": "MIT", + "peerDependencies": { + "valtio": "*" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", + "license": "MIT" + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eciesjs": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.16.tgz", + "integrity": "sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==", + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.4", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, + "node_modules/eciesjs/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.33.0.tgz", + "integrity": "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-block-tracker": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-7.1.0.tgz", + "integrity": "sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg==", + "license": "MIT", + "dependencies": { + "@metamask/eth-json-rpc-provider": "^1.0.0", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^5.0.1", + "json-rpc-random-id": "^1.0.1", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-block-tracker/node_modules/@metamask/utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", + "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.1.2", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "semver": "^7.3.8", + "superstruct": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-block-tracker/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eth-block-tracker/node_modules/superstruct": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz", + "integrity": "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-json-rpc-filters": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz", + "integrity": "sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig==", + "license": "ISC", + "dependencies": { + "@metamask/safe-event-emitter": "^3.0.0", + "async-mutex": "^0.2.6", + "eth-query": "^2.1.2", + "json-rpc-engine": "^6.1.0", + "pify": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-json-rpc-filters/node_modules/pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eth-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", + "integrity": "sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA==", + "license": "ISC", + "dependencies": { + "json-rpc-random-id": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "node_modules/eth-rpc-errors": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", + "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", + "license": "MIT", + "dependencies": { + "fast-safe-stringify": "^2.0.6" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/extension-port-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", + "integrity": "sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==", + "license": "ISC", + "dependencies": { + "readable-stream": "^3.6.2 || ^4.4.2", + "webextension-polyfill": ">=0.10.0 <1.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT" + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/h3": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", + "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", + "license": "Apache-2.0" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "stream-json": "^1.9.1", + "uuid": "^8.3.2", + "ws": "^7.5.10" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "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 + } + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-rpc-engine": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", + "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", + "license": "ISC", + "dependencies": { + "@metamask/safe-event-emitter": "^2.0.0", + "eth-rpc-errors": "^4.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/json-rpc-engine/node_modules/@metamask/safe-event-emitter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", + "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==", + "license": "ISC" + }, + "node_modules/json-rpc-random-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", + "integrity": "sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==", + "license": "ISC" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keyvaluestorage-interface": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", + "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==", + "license": "MIT" + }, + "node_modules/lit": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", + "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/media-query-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz", + "integrity": "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + } + }, + "node_modules/micro-ftch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", + "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mipd": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", + "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/modern-ahocorasick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.1.0.tgz", + "integrity": "sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obj-multiplex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/obj-multiplex/-/obj-multiplex-1.0.0.tgz", + "integrity": "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==", + "license": "ISC", + "dependencies": { + "end-of-stream": "^1.4.0", + "once": "^1.4.0", + "readable-stream": "^2.3.3" + } + }, + "node_modules/obj-multiplex/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/obj-multiplex/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/obj-multiplex/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/obj-multiplex/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/on-exit-leak-free": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", + "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==", + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-fetch": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.13.8.tgz", + "integrity": "sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.0.15" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.15.tgz", + "integrity": "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==", + "license": "MIT" + }, + "node_modules/ox": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", + "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pino": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-7.11.0.tgz", + "integrity": "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.0.0", + "on-exit-leak-free": "^0.2.0", + "pino-abstract-transport": "v0.5.0", + "pino-std-serializers": "^4.0.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.1.0", + "safe-stable-stringify": "^2.1.0", + "sonic-boom": "^2.2.1", + "thread-stream": "^0.15.1" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", + "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.2", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", + "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==", + "license": "MIT" + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/pony-cause": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", + "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", + "license": "0BSD", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.24.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz", + "integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", + "license": "MIT" + }, + "node_modules/proxy-compare": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", + "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qr": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/qr/-/qr-0.5.4.tgz", + "integrity": "sha512-gjVMHOt7CX+BQd7JLQ9fnS4kJK4Lj4u+Conq52tcCbW7YH3mATTtBbTMA+7cQ1rKOkDo61olFHJReawe+XFxIA==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 20.19.0" + } + }, + "node_modules/qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz", + "integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rpc-websockets": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.2.tgz", + "integrity": "sha512-VuW2xJDnl1k8n8kjbdRSWawPRkwaVqUQNjE1TdeTawf0y0abGhtVJFTXCLfgpgGDBkO/Fj6kny8Dc/nvOW78MA==", + "license": "LGPL-3.0-only", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^8.3.4", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, + "node_modules/rpc-websockets/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", + "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/thread-stream": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", + "integrity": "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.1.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/uint8arrays": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", + "integrity": "sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/valtio": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", + "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", + "license": "MIT", + "dependencies": { + "derive-valtio": "0.1.0", + "proxy-compare": "2.6.0", + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/valtio/node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/viem": { + "version": "2.44.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.44.2.tgz", + "integrity": "sha512-nHY872t/T3flLpVsnvQT/89bwbrJwxaL917FDv7Oxy4E5FWIFkokRQOKXG3P+hgl30QYVZxi9o2SUHLnebycxw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.11.3", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wagmi": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.19.5.tgz", + "integrity": "sha512-RQUfKMv6U+EcSNNGiPbdkDtJwtuFxZWLmvDiQmjjBgkuPulUwDJsKhi7gjynzJdsx2yDqhHCXkKsbbfbIsHfcQ==", + "license": "MIT", + "dependencies": { + "@wagmi/connectors": "6.2.0", + "@wagmi/core": "2.22.1", + "use-sync-external-store": "1.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/webextension-polyfill": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", + "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", + "license": "MPL-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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 + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..3d0d7580 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "og-deployer-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@rainbow-me/rainbowkit": "^2.1.6", + "@tanstack/react-query": "^5.56.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.20.0", + "wagmi": "^2.12.7" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^7.3.1" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 00000000..e58f4ca2 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,690 @@ +import { useEffect, useMemo, useState } from 'react'; +import { ConnectButton } from '@rainbow-me/rainbowkit'; +import { + encodeAbiParameters, + encodeFunctionData, + concatHex, + hexToSignature, + isAddress, + numberToHex, + signatureToHex, + stringToHex, + zeroAddress, +} from 'viem'; +import { usePublicClient, useWalletClient } from 'wagmi'; + +const safeProxyFactoryAbi = [ + { + type: 'function', + name: 'createProxyWithNonce', + stateMutability: 'nonpayable', + inputs: [ + { name: 'singleton', type: 'address' }, + { name: 'initializer', type: 'bytes' }, + { name: 'saltNonce', type: 'uint256' }, + ], + outputs: [{ name: 'proxy', type: 'address' }], + }, +]; + +const moduleProxyFactoryAbi = [ + { + type: 'function', + name: 'deployModule', + stateMutability: 'nonpayable', + inputs: [ + { name: 'masterCopy', type: 'address' }, + { name: 'initializer', type: 'bytes' }, + { name: 'saltNonce', type: 'uint256' }, + ], + outputs: [{ name: 'proxy', type: 'address' }], + }, +]; + +const safeAbi = [ + { + type: 'function', + name: 'setup', + stateMutability: 'nonpayable', + inputs: [ + { name: '_owners', type: 'address[]' }, + { name: '_threshold', type: 'uint256' }, + { name: 'to', type: 'address' }, + { name: 'data', type: 'bytes' }, + { name: 'fallbackHandler', type: 'address' }, + { name: 'paymentToken', type: 'address' }, + { name: 'payment', type: 'uint256' }, + { name: 'paymentReceiver', type: 'address' }, + ], + outputs: [], + }, + { + type: 'function', + name: 'nonce', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'getTransactionHash', + stateMutability: 'view', + inputs: [ + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'operation', type: 'uint8' }, + { name: 'safeTxGas', type: 'uint256' }, + { name: 'baseGas', type: 'uint256' }, + { name: 'gasPrice', type: 'uint256' }, + { name: 'gasToken', type: 'address' }, + { name: 'refundReceiver', type: 'address' }, + { name: '_nonce', type: 'uint256' }, + ], + outputs: [{ type: 'bytes32' }], + }, + { + type: 'function', + name: 'execTransaction', + stateMutability: 'payable', + inputs: [ + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'operation', type: 'uint8' }, + { name: 'safeTxGas', type: 'uint256' }, + { name: 'baseGas', type: 'uint256' }, + { name: 'gasPrice', type: 'uint256' }, + { name: 'gasToken', type: 'address' }, + { name: 'refundReceiver', type: 'address' }, + { name: 'signatures', type: 'bytes' }, + ], + outputs: [{ type: 'bool' }], + }, + { + type: 'function', + name: 'addOwnerWithThreshold', + stateMutability: 'nonpayable', + inputs: [ + { name: 'owner', type: 'address' }, + { name: '_threshold', type: 'uint256' }, + ], + outputs: [], + }, + { + type: 'function', + name: 'removeOwner', + stateMutability: 'nonpayable', + inputs: [ + { name: 'prevOwner', type: 'address' }, + { name: 'owner', type: 'address' }, + { name: '_threshold', type: 'uint256' }, + ], + outputs: [], + }, +]; + +const enableModuleAbi = [ + { + type: 'function', + name: 'enableModule', + stateMutability: 'nonpayable', + inputs: [{ name: 'module', type: 'address' }], + outputs: [], + }, +]; + +const ogSetupAbi = [ + { + type: 'function', + name: 'setUp', + stateMutability: 'nonpayable', + inputs: [{ name: 'initParams', type: 'bytes' }], + outputs: [], + }, +]; + +const MODULE_PROXY_FACTORY_BYTECODE = + '0x60808060405234610016576102e4908161001b8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63f1ab873c14610024575f80fd5b346100ce5760603660031901126100ce576004356001600160a01b03811681036100ce5760243567ffffffffffffffff81116100ce57366023820112156100ce5780600401359161007483610129565b6100816040519182610107565b83815236602485850101116100ce575f6020856100ca9660246100b09701838601378301015260443591610174565b6040516001600160a01b0390911681529081906020820190565b0390f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6060810190811067ffffffffffffffff82111761010257604052565b6100d2565b90601f8019910116810190811067ffffffffffffffff82111761010257604052565b67ffffffffffffffff811161010257601f01601f191660200190565b3d1561016f573d9061015682610129565b916101646040519384610107565b82523d5f602084013e565b606090565b90929183519060208501918220604091825190602082019283528382015282815261019e816100e6565b5190206001600160a01b0384811694909190851561029657835172602d8060093d393df3363d3d373d3d3d363d7360681b6020820190815260609290921b6bffffffffffffffffffffffff191660338201526e5af43d82803e903d91602b57fd5bf360881b604782015260368152610215816100e6565b51905ff590811692831561027457815f92918380939951925af1610237610145565b501561026457507f2150ada912bf189ed721c44211199e270903fc88008c2a1e1e889ef30fe67c5f5f80a3565b51637dabd39960e01b8152600490fd5b50905163371e9e8960e21b81526001600160a01b039091166004820152602490fd5b8351633202e20d60e21b815260048101879052602490fdfea26469706673582212208f37f4bfb66727d4e6c07c613af0febf39dcd35dcf8d6037c9da73384d61b55764736f6c63430008170033'; + +function readEnv(key) { + if (typeof process !== 'undefined' && process?.env?.[key]) { + return process.env[key]; + } + + if (typeof import.meta !== 'undefined') { + const metaEnv = import.meta?.env; + if (metaEnv?.[key]) { + return metaEnv[key]; + } + } + + return undefined; +} + +function readEnvWithPrefixes(key) { + return readEnv(key) ?? readEnv(`VITE_${key}`) ?? readEnv(`NEXT_PUBLIC_${key}`); +} + +const defaultModuleProxyFactory = readEnvWithPrefixes('MODULE_PROXY_FACTORY') ?? ''; + +const defaults = { + safeSingleton: '0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552', + safeProxyFactory: '0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2', + safeFallbackHandler: '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4', + ogMasterCopy: '0x28CeBFE94a03DbCA9d17143e9d2Bd1155DC26D5d', + ogIdentifier: 'ASSERT_TRUTH2', + ogLiveness: '172800', + collateral: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + bondAmount: (250n * 10n ** 6n).toString(), + safeSaltNonce: '1', + ogSaltNonce: '1', + moduleProxyFactory: defaultModuleProxyFactory, +}; + +const zeroLike = '0x0000000000000000000000000000000000000000'; +const BURN_OWNER = '0x000000000000000000000000000000000000dEaD'; +const SENTINEL_OWNERS = '0x0000000000000000000000000000000000000001'; + +function App() { + const publicClient = usePublicClient(); + const { data: walletClient } = useWalletClient(); + const [form, setForm] = useState({ + rules: '', + collateral: defaults.collateral, + bondAmount: defaults.bondAmount, + liveness: defaults.ogLiveness, + identifier: defaults.ogIdentifier, + safeSaltNonce: defaults.safeSaltNonce, + ogSaltNonce: defaults.ogSaltNonce, + safeSingleton: defaults.safeSingleton, + safeProxyFactory: defaults.safeProxyFactory, + safeFallbackHandler: defaults.safeFallbackHandler, + ogMasterCopy: defaults.ogMasterCopy, + moduleProxyFactory: defaults.moduleProxyFactory, + }); + const [deployment, setDeployment] = useState({ + moduleProxyFactory: '', + safe: '', + ogModule: '', + }); + const [txHashes, setTxHashes] = useState({ + moduleProxyFactory: '', + safeProxy: '', + ogModule: '', + enableModule: '', + }); + const [error, setError] = useState(''); + const [status, setStatus] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const isConnected = Boolean(walletClient?.account?.address); + + const missingWallet = !isConnected || !publicClient; + + const onChange = (event) => { + const { name, value } = event.target; + setForm((prev) => ({ ...prev, [name]: value })); + }; + + const validatedAddresses = useMemo(() => { + const required = { + collateral: form.collateral, + safeSingleton: form.safeSingleton, + safeProxyFactory: form.safeProxyFactory, + safeFallbackHandler: form.safeFallbackHandler, + ogMasterCopy: form.ogMasterCopy, + }; + const optional = { + moduleProxyFactory: form.moduleProxyFactory, + }; + const invalid = Object.entries(required).filter(([, value]) => !isAddress(value || '')); + const invalidOptional = Object.entries(optional).filter(([, value]) => value && !isAddress(value)); + return { invalid, invalidOptional }; + }, [form]); + + const handleDeploy = async () => { + if (missingWallet) { + setError('Connect a wallet to deploy.'); + return; + } + + if (!form.rules.trim()) { + setError('OG rules are required.'); + return; + } + + if (!form.collateral.trim()) { + setError('Collateral address is required.'); + return; + } + + if (!form.bondAmount.trim()) { + setError('Bond amount is required.'); + return; + } + + if (!form.liveness.trim()) { + setError('Liveness is required.'); + return; + } + + if (validatedAddresses.invalid.length || validatedAddresses.invalidOptional.length) { + setError('One or more addresses are invalid.'); + return; + } + + setIsSubmitting(true); + setError(''); + setStatus('Preparing deployment...'); + setDeployment({ moduleProxyFactory: '', safe: '', ogModule: '' }); + setTxHashes({ moduleProxyFactory: '', safeProxy: '', ogModule: '', enableModule: '' }); + + try { + const account = walletClient.account.address; + const safeSaltNonce = BigInt(form.safeSaltNonce || '0'); + const ogSaltNonce = BigInt(form.ogSaltNonce || '0'); + const bondAmount = BigInt(form.bondAmount || '0'); + const liveness = BigInt(form.liveness || '0'); + const identifier = stringToHex(form.identifier, { size: 32 }); + let moduleProxyFactory = form.moduleProxyFactory; + + if (!moduleProxyFactory) { + setStatus('Deploying ModuleProxyFactory...'); + const deployTx = await walletClient.deployContract({ + abi: moduleProxyFactoryAbi, + bytecode: MODULE_PROXY_FACTORY_BYTECODE, + account, + }); + setTxHashes((prev) => ({ ...prev, moduleProxyFactory: deployTx })); + const receipt = await publicClient.waitForTransactionReceipt({ hash: deployTx }); + moduleProxyFactory = receipt.contractAddress ?? ''; + setForm((prev) => ({ ...prev, moduleProxyFactory })); + } + + const safeInitializer = encodeFunctionData({ + abi: safeAbi, + functionName: 'setup', + args: [ + [account], + 1n, + zeroAddress, + '0x', + form.safeFallbackHandler, + zeroAddress, + 0n, + zeroAddress, + ], + }); + + setStatus('Deploying Safe proxy...'); + const safeSimulation = await publicClient.simulateContract({ + account, + address: form.safeProxyFactory, + abi: safeProxyFactoryAbi, + functionName: 'createProxyWithNonce', + args: [form.safeSingleton, safeInitializer, safeSaltNonce], + }); + const safeTxHash = await walletClient.writeContract(safeSimulation.request); + setTxHashes((prev) => ({ ...prev, safeProxy: safeTxHash })); + await publicClient.waitForTransactionReceipt({ hash: safeTxHash }); + const safeProxy = safeSimulation.result; + + setStatus('Deploying Optimistic Governor module...'); + const ogInitParams = encodeAbiParameters( + [ + { name: 'owner', type: 'address' }, + { name: 'collateral', type: 'address' }, + { name: 'bondAmount', type: 'uint256' }, + { name: 'rules', type: 'string' }, + { name: 'identifier', type: 'bytes32' }, + { name: 'liveness', type: 'uint64' }, + ], + [safeProxy, form.collateral, bondAmount, form.rules, identifier, liveness] + ); + + const ogInitializerCall = encodeFunctionData({ + abi: ogSetupAbi, + functionName: 'setUp', + args: [ogInitParams], + }); + + const ogSimulation = await publicClient.simulateContract({ + account, + address: moduleProxyFactory, + abi: moduleProxyFactoryAbi, + functionName: 'deployModule', + args: [form.ogMasterCopy, ogInitializerCall, ogSaltNonce], + }); + const ogTxHash = await walletClient.writeContract(ogSimulation.request); + setTxHashes((prev) => ({ ...prev, ogModule: ogTxHash })); + await publicClient.waitForTransactionReceipt({ hash: ogTxHash }); + const ogModule = ogSimulation.result; + + setStatus('Signing Safe enableModule transaction...'); + const safeNonce = await publicClient.readContract({ + address: safeProxy, + abi: safeAbi, + functionName: 'nonce', + }); + + const enableModuleCallData = encodeFunctionData({ + abi: enableModuleAbi, + functionName: 'enableModule', + args: [ogModule], + }); + + const txHash = await publicClient.readContract({ + address: safeProxy, + abi: safeAbi, + functionName: 'getTransactionHash', + args: [ + safeProxy, + 0n, + enableModuleCallData, + 0, + 0n, + 0n, + 0n, + zeroAddress, + zeroAddress, + safeNonce, + ], + }); + + const signature = await walletClient.signMessage({ message: { raw: txHash } }); + const { r, s, v } = hexToSignature(signature); + const safeV = (v >= 27n ? v : v + 27n) + 4n; // eth_sign flavor + const packedSignature = concatHex([r, s, numberToHex(safeV, { size: 1 })]); + + setStatus('Enabling module on the Safe...'); + const execSimulation = await publicClient.simulateContract({ + account, + address: safeProxy, + abi: safeAbi, + functionName: 'execTransaction', + args: [ + safeProxy, + 0n, + enableModuleCallData, + 0, + 0n, + 0n, + 0n, + zeroAddress, + zeroAddress, + packedSignature, + ], + }); + + const enableTxHash = await walletClient.writeContract(execSimulation.request); + setTxHashes((prev) => ({ ...prev, enableModule: enableTxHash })); + await publicClient.waitForTransactionReceipt({ hash: enableTxHash }); + + setStatus('Setting burn address as sole Safe owner...'); + const addOwnerCallData = encodeFunctionData({ + abi: safeAbi, + functionName: 'addOwnerWithThreshold', + args: [BURN_OWNER, 1n], + }); + + const addOwnerNonce = await publicClient.readContract({ + address: safeProxy, + abi: safeAbi, + functionName: 'nonce', + }); + + const addOwnerTxHash = await publicClient.readContract({ + address: safeProxy, + abi: safeAbi, + functionName: 'getTransactionHash', + args: [ + safeProxy, + 0n, + addOwnerCallData, + 0, + 0n, + 0n, + 0n, + zeroAddress, + zeroAddress, + addOwnerNonce, + ], + }); + + const addOwnerSignature = await walletClient.signMessage({ message: { raw: addOwnerTxHash } }); + const { r: addOwnerR, s: addOwnerS, v: addOwnerVRaw } = hexToSignature(addOwnerSignature); + const addOwnerV = (addOwnerVRaw >= 27n ? addOwnerVRaw : addOwnerVRaw + 27n) + 4n; + const addOwnerPackedSignature = concatHex([ + addOwnerR, + addOwnerS, + numberToHex(addOwnerV, { size: 1 }), + ]); + + const addOwnerExec = await publicClient.simulateContract({ + account, + address: safeProxy, + abi: safeAbi, + functionName: 'execTransaction', + args: [ + safeProxy, + 0n, + addOwnerCallData, + 0, + 0n, + 0n, + 0n, + zeroAddress, + zeroAddress, + addOwnerPackedSignature, + ], + }); + + const addOwnerTx = await walletClient.writeContract(addOwnerExec.request); + await publicClient.waitForTransactionReceipt({ hash: addOwnerTx }); + + setStatus('Removing deployer from Safe owners...'); + const removeOwnerCallData = encodeFunctionData({ + abi: safeAbi, + functionName: 'removeOwner', + args: [SENTINEL_OWNERS, account, 1n], + }); + + const removeOwnerNonce = await publicClient.readContract({ + address: safeProxy, + abi: safeAbi, + functionName: 'nonce', + }); + + const removeOwnerTxHash = await publicClient.readContract({ + address: safeProxy, + abi: safeAbi, + functionName: 'getTransactionHash', + args: [ + safeProxy, + 0n, + removeOwnerCallData, + 0, + 0n, + 0n, + 0n, + zeroAddress, + zeroAddress, + removeOwnerNonce, + ], + }); + + const removeOwnerSignature = await walletClient.signMessage({ message: { raw: removeOwnerTxHash } }); + const { r: removeOwnerR, s: removeOwnerS, v: removeOwnerVRaw } = hexToSignature(removeOwnerSignature); + const removeOwnerV = (removeOwnerVRaw >= 27n ? removeOwnerVRaw : removeOwnerVRaw + 27n) + 4n; + const removeOwnerPackedSignature = concatHex([ + removeOwnerR, + removeOwnerS, + numberToHex(removeOwnerV, { size: 1 }), + ]); + + const removeOwnerExec = await publicClient.simulateContract({ + account, + address: safeProxy, + abi: safeAbi, + functionName: 'execTransaction', + args: [ + safeProxy, + 0n, + removeOwnerCallData, + 0, + 0n, + 0n, + 0n, + zeroAddress, + zeroAddress, + removeOwnerPackedSignature, + ], + }); + + const removeOwnerTx = await walletClient.writeContract(removeOwnerExec.request); + await publicClient.waitForTransactionReceipt({ hash: removeOwnerTx }); + + setDeployment({ + moduleProxyFactory, + safe: safeProxy, + ogModule, + }); + setStatus('Deployment complete (burn address is sole Safe owner).'); + } catch (err) { + setError(err?.shortMessage || err?.message || 'Deployment failed.'); + setStatus(''); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+
+

OG Deployer

+

Deploy Safe + Optimistic Governor

+

+ Mirrors the DeploySafeWithOptimisticGovernor.s.sol flow with UI-driven parameters. +

+
+ +
+ +
+

Governance Parameters

+
+