From bb0b87bd1afa7a8ff2e38ff4add5cdb6e0d7519e Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:50:59 -0600 Subject: [PATCH 01/31] Restructure loan and bridge examples and add shared utils --- .github/workflows/bridge-offchain-worker.yml | 10 +- .../workflows/custom-contracts-bridging.yml | 12 +- .github/workflows/hello-bridge.yml | 10 +- .github/workflows/loan-flow.yml | 10 +- .github/workflows/solidity.yml | 6 +- README.md | 86 +- bridge/.env.example | 30 + .../bridge-offchain-worker}/README.md | 2 +- .../bridge-offchain-worker}/worker.ts | 4 +- {contracts => bridge/contracts}/DEPLOY.md | 16 +- .../contracts}/abi-creator.sh | 7 +- .../contracts}/abi/ASCBase.json | 0 .../contracts}/abi/ASCMinter.json | 0 .../contracts}/abi/BridgeTestToken.json | 0 .../contracts}/abi/MintableToken.json | 0 .../contracts}/abi/VerifierInterface.json | 0 bridge/contracts/abi_generation.md | 10 + .../contracts}/sol/ASCBase.sol | 0 .../contracts}/sol/ASCMinter.sol | 2 +- .../contracts}/sol/BridgeTestToken.sol | 0 .../contracts}/sol/MintableToken.sol | 0 .../contracts}/sol/VerifierInterface.sol | 0 .../custom-contracts-bridging}/README.md | 12 +- .../custom-contracts-bridging/submit_query.ts | 20 + bridge/foundry.toml | 13 + .../hello-bridge}/README.md | 0 bridge/hello-bridge/submit_query.ts | 20 + contracts/abi_generation.md | 16 - foundry.toml | 6 +- hello-bridge/submit_query.ts | 98 - loan/.env.example | 25 + loan/contracts/abi-creator.sh | 17 + .../contracts}/abi/ASCLoanManager.json | 0 .../contracts}/abi/AuxiliaryLoanContract.json | 0 .../contracts}/abi/LoanTypes.json | 0 .../contracts}/abi/TestERC20.json | 0 loan/contracts/abi_generation.md | 10 + .../contracts}/sol/ASCLoanManager.sol | 7 +- .../contracts}/sol/AuxiliaryLoanContract.sol | 0 .../contracts}/sol/LoanTypes.sol | 0 .../contracts}/sol/TestERC20.sol | 0 loan/foundry.toml | 14 + {loan-flow => loan/scripts}/README.md | 14 +- {loan-flow => loan/scripts}/authorize.ts | 2 +- {loan-flow => loan/scripts}/fund_loan.ts | 2 +- {loan-flow => loan/scripts}/inspect.ts | 2 +- {loan-flow => loan/scripts}/register.ts | 2 +- .../scripts}/register_source_contract.ts | 2 +- {loan-flow => loan/scripts}/repay_loan.ts | 2 +- {loan-flow => loan/scripts}/worker.ts | 2 +- package-lock.json | 2139 +++++++++++++++++ package.json | 24 +- .../submit_bridge_query.ts | 188 +- {utils => shared/utils}/check_balance.js | 0 {utils => shared/utils}/index.ts | 0 yarn.lock | 579 ++--- 56 files changed, 2716 insertions(+), 705 deletions(-) create mode 100644 bridge/.env.example rename {bridge-offchain-worker => bridge/bridge-offchain-worker}/README.md (99%) rename {bridge-offchain-worker => bridge/bridge-offchain-worker}/worker.ts (98%) rename {contracts => bridge/contracts}/DEPLOY.md (87%) rename {contracts => bridge/contracts}/abi-creator.sh (68%) mode change 100755 => 100644 rename {contracts => bridge/contracts}/abi/ASCBase.json (100%) rename {contracts => bridge/contracts}/abi/ASCMinter.json (100%) rename {contracts => bridge/contracts}/abi/BridgeTestToken.json (100%) rename {contracts => bridge/contracts}/abi/MintableToken.json (100%) rename {contracts => bridge/contracts}/abi/VerifierInterface.json (100%) create mode 100644 bridge/contracts/abi_generation.md rename {contracts => bridge/contracts}/sol/ASCBase.sol (100%) rename {contracts => bridge/contracts}/sol/ASCMinter.sol (97%) rename {contracts => bridge/contracts}/sol/BridgeTestToken.sol (100%) rename {contracts => bridge/contracts}/sol/MintableToken.sol (100%) rename {contracts => bridge/contracts}/sol/VerifierInterface.sol (100%) rename {custom-contracts-bridging => bridge/custom-contracts-bridging}/README.md (95%) create mode 100644 bridge/custom-contracts-bridging/submit_query.ts create mode 100644 bridge/foundry.toml rename {hello-bridge => bridge/hello-bridge}/README.md (100%) create mode 100644 bridge/hello-bridge/submit_query.ts delete mode 100644 contracts/abi_generation.md delete mode 100644 hello-bridge/submit_query.ts create mode 100644 loan/.env.example create mode 100644 loan/contracts/abi-creator.sh rename {contracts => loan/contracts}/abi/ASCLoanManager.json (100%) rename {contracts => loan/contracts}/abi/AuxiliaryLoanContract.json (100%) rename {contracts => loan/contracts}/abi/LoanTypes.json (100%) rename {contracts => loan/contracts}/abi/TestERC20.json (100%) create mode 100644 loan/contracts/abi_generation.md rename {contracts => loan/contracts}/sol/ASCLoanManager.sol (97%) rename {contracts => loan/contracts}/sol/AuxiliaryLoanContract.sol (100%) rename {contracts => loan/contracts}/sol/LoanTypes.sol (100%) rename {contracts => loan/contracts}/sol/TestERC20.sol (100%) create mode 100644 loan/foundry.toml rename {loan-flow => loan/scripts}/README.md (96%) rename {loan-flow => loan/scripts}/authorize.ts (96%) rename {loan-flow => loan/scripts}/fund_loan.ts (98%) rename {loan-flow => loan/scripts}/inspect.ts (98%) rename {loan-flow => loan/scripts}/register.ts (98%) rename {loan-flow => loan/scripts}/register_source_contract.ts (97%) rename {loan-flow => loan/scripts}/repay_loan.ts (98%) rename {loan-flow => loan/scripts}/worker.ts (99%) create mode 100644 package-lock.json rename custom-contracts-bridging/submit_query.ts => shared/submit_bridge_query.ts (55%) rename {utils => shared/utils}/check_balance.js (100%) rename {utils => shared/utils}/index.ts (100%) diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index f0715105..a751f6e8 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/TestERC20.sol:TestERC20 2>&1) +loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) +node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -217,8 +217,8 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ - contracts/sol/ASCMinter.sol:ASCMinter 2>&1) +--libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "asc_minter_contract_address=$ASC_MINTER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -228,7 +228,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/BridgeTestToken.sol:BridgeTestToken \ +bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_MINTER_CONTRACT_ADDRESS" 2>&1) echo "$OUTPUT" ASC_MINTABLE_TOKEN=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index f1cdf554..1ecaad9c 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/TestERC20.sol:TestERC20 2>&1) +loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,12 +208,12 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) +node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "INFO: Modify ASCMinter.sol to mint 2x" - sed -i "s/ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue);/ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue * 2);/" contracts/sol/ASCMinter.sol + sed -i "s/ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue);/ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue * 2);/" bridge/contracts/sol/ASCMinter.sol git diff echo "INFO: deploy ASCMinter.sol to Creditcoin chain" @@ -221,8 +221,8 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ - contracts/sol/ASCMinter.sol:ASCMinter 2>&1) +--libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "asc_minter_contract_address=$ASC_MINTER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -232,7 +232,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/BridgeTestToken.sol:BridgeTestToken \ +bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_MINTER_CONTRACT_ADDRESS" 2>&1) echo "$OUTPUT" ASC_MINTABLE_TOKEN=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index df74f99a..8ec7bb03 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key ${{ steps.env.outputs.source_chain_private_key }} \ - contracts/sol/TestERC20.sol:TestERC20 2>&1) +loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) +node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -217,8 +217,8 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ - contracts/sol/ASCMinter.sol:ASCMinter 2>&1) +--libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "minter_contract_address=$ASC_MINTER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -228,7 +228,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/BridgeTestToken.sol:BridgeTestToken \ +bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_MINTER_CONTRACT_ADDRESS" 2>&1) echo "$OUTPUT" ASC_MINTABLE_TOKEN=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 8dd33790..7b58e13a 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/TestERC20.sol:TestERC20 2>&1) +loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract 2>&1) +loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract 2>&1) echo "$OUTPUT" AUX_LOAN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "aux_loan_contract_address=$AUX_LOAN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -218,7 +218,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) +node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -227,8 +227,8 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ - contracts/sol/ASCLoanManager.sol:ASCLoanManager 2>&1) +--libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager 2>&1) echo "$OUTPUT" ASC_LOAN_MANAGER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "asc_loan_manager_contract_address=$ASC_LOAN_MANAGER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index bb63de21..be59caee 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -35,7 +35,8 @@ jobs: run: | yarn install - ~/.foundry/bin/forge build + ~/.foundry/bin/forge build --root bridge + ~/.foundry/bin/forge build --root loan check-abi-on-disk: runs-on: ubuntu-26.04 @@ -58,7 +59,8 @@ jobs: yarn install solc --version - ./contracts/abi-creator.sh + ./bridge/contracts/abi-creator.sh + ./loan/contracts/abi-creator.sh - name: Check for mismatch run: | diff --git a/README.md b/README.md index 6b164c5c..fcb7a000 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,68 @@ # ๐ŸŒ‰ ASC Testnet Bridge Examples ๐ŸŒ‰ -This repository is designed as a starting point for users and builders alike to explore the new -Creditcoin cross-chain features brought forwards in the new `CC3 Testnet` update. Learn how to use -the Creditcoin Decentralized Oracle through a series of guided tutorials where you get to set up and -interact with your own decentralized bridge! +This repository is designed as a starting point for users and builders alike to explore Creditcoin +cross-chain features on USC devnet. Learn how to use the Creditcoin Decentralized Oracle through +guided tutorials where you set up and interact with your own decentralized bridge and loan examples. -## Before you start! +## Repository layout -Before attempting any of the tutorials, make sure the following are installed and available in your system: +``` +usc-testnet-bridge-examples/ +โ”œโ”€โ”€ bridge/ # ASC bridge tutorials + bridge contracts +โ”‚ โ”œโ”€โ”€ contracts/ # ASCBase, ASCMinter, BridgeTestToken, โ€ฆ (local ASCBase; EvmV1Decoder from @gluwa/usc-contracts) +โ”‚ โ”œโ”€โ”€ hello-bridge/ +โ”‚ โ”œโ”€โ”€ custom-contracts-bridging/ +โ”‚ โ””โ”€โ”€ bridge-offchain-worker/ +โ”œโ”€โ”€ loan/ # Cross-chain loan tutorial + loan contracts +โ”‚ โ”œโ”€โ”€ contracts/ # ASCLoanManager, AuxiliaryLoanContract, TestERC20, โ€ฆ +โ”‚ โ””โ”€โ”€ scripts/ # Loan flow CLI scripts +โ””โ”€โ”€ shared/utils/ # Proof builder + submit helpers shared by both examples +``` + +## Before you start + +Before attempting any of the tutorials, make sure the following are installed: - [yarn] - [foundry] -After that install the required dependencies to run the examples, to do so simply run the -following command in the root of this repo: +Install dependencies from the repository root: ```sh yarn ``` -You will also need to set up the right version of foundry with `foundryup`: +Set up Foundry: ```bash foundryup --version v1.2.3 - ``` -Next, create your own `.env` from the template. `.env` holds your wallet private key, so it is -git-ignored and never committed โ€” `.env.example` is the tracked copy, which carries only public -network defaults: +Copy and fill in environment files for the examples you plan to run. `.env` holds your wallet +private key, so it is git-ignored and never committed โ€” `.env.example` is the tracked copy, which +carries only public network defaults: -```bash -cp .env.example .env -``` +- Bridge tutorials: `bridge/.env.example` โ†’ `bridge/.env` (or root `.env`) +- Loan tutorial: `loan/.env.example` โ†’ `loan/.env` (or root `.env`) > [!CAUTION] > Keep your private key in `.env` only. Never put it in `.env.example`, which is tracked by git. -Finally, source the `.env` file to load your configuration globally for all examples: +Then source your env file to load configuration for all examples: ```bash source .env ``` -This ensures that all tutorial commands can access your wallet private key and RPC URLs without needing to manually substitute them. - ## Tutorials -Each tutorial is built to be as self-contained as possible. However, we still recommend you go -through them in the following order: +We recommend going through them in this order: 1. ๐Ÿ“š [Hello Bridge] 2. ๐Ÿ“š [Custom Contracts Bridging] 3. ๐Ÿ“š [Bridge Offchain Worker] 4. ๐Ÿ“š [Loan Flow] -## Content - -Below is brief overview of each tutorial's content. - -### Hello Bridge - -Learn how to use the Creditcoin Decentralized Oracle from the perspective of an end user. [Hello -Bridge] makes use of pre-existing smart contracts on the Sepolia and Creditcoin Testnet so as to -minimize the amount of setup needed. - -### Custom Contracts Bridging - -Learn how to setup your own trustless cross-chain bridging logic by deploying your own contracts. -[Custom Contracts Bridging] teaches you the perspective of a DApp builder by showing you how to set -up custom logic across each chain you are deploying to. - -### Bridge Offchain Worker - -Streamline UX by automating away most transactions in the bridging process. [Bridge Offchain Worker] -shows you how offchain workers can be used to simplify the user flow of your cross-chain DApp. - -### Loan Flow - -Building on top of what we learned with the [Bridge Offchain Worker] we take it a step further by -implementing a loaning system using both a ASC contract, a source chain helper and an offchain worker -to seamlesly coordinate between the two, and of course making use of our Oracle proving capabilities to -add the final touches of trust into the process. - ## External Resources - ๐Ÿ“š [ASC Architecture Overview] @@ -91,10 +71,10 @@ add the final touches of trust into the process. [yarn]: https://yarnpkg.com/getting-started/install [foundry]: https://getfoundry.sh/ -[Hello Bridge]: ./hello-bridge/README.md -[Custom Contracts Bridging]: ./custom-contracts-bridging/README.md -[Bridge Offchain Worker]: ./bridge-offchain-worker/README.md -[Loan Flow]: ./loan-flow/README.md +[Hello Bridge]: ./bridge/hello-bridge/README.md +[Custom Contracts Bridging]: ./bridge/custom-contracts-bridging/README.md +[Bridge Offchain Worker]: ./bridge/bridge-offchain-worker/README.md +[Loan Flow]: ./loan/scripts/README.md [ASC Architecture Overview]: https://docs.attestcoin.org/attestcoin-protocol/architecture [DApp Builder Infrastructure]: https://docs.attestcoin.org/attestcoin-protocol/dapp-builder-infrastructure [Attestcoin Readability Subsystems]: https://docs.attestcoin.org/attestcoin-protocol/attestcoin-readability diff --git a/bridge/.env.example b/bridge/.env.example new file mode 100644 index 00000000..65aeb94f --- /dev/null +++ b/bridge/.env.example @@ -0,0 +1,30 @@ +# Bridge example environment +# Copy to bridge/.env or merge into the repository root .env + +## Pre-deployed / network defaults (hello-bridge uses these as-is on USC devnet) +# Source chain key for Sepolia on USC devnet (chain ID 11155111 on Ethereum) +SOURCE_CHAIN_KEY=8 +# Proof builder for USC devnet +PROOF_BUILDER_URL="https://prover.usc-devnet.creditcoin.network/" +# Pre-deployed burner ERC20 on Sepolia (hello-bridge tutorial) +SOURCE_CHAIN_CONTRACT_ADDRESS="" +# Pre-deployed ASC minter on USC devnet (hello-bridge tutorial) +ASC_MINTER_CONTRACT_ADDRESS="" +# Pre-deployed wrapped token on USC devnet (hello-bridge tutorial) +ASC_MINTABLE_TOKEN="" +# USC devnet RPC (USC = network layer name only) +CREDITCOIN_RPC_URL="https://rpc.usc-devnet.creditcoin.network" + +## User-provided (required for all bridge tutorials) +# Sepolia (or other source chain) RPC URL +SOURCE_CHAIN_RPC_URL="" +# Wallet used on USC devnet and source chain +CREDITCOIN_WALLET_PRIVATE_KEY="" + +## User-deployed (custom-contracts-bridging + offchain worker) +# Your burner ERC20 on the source chain +SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" +# Your ASCMinter on USC devnet +ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" +# Your wrapped ASCMintableToken on USC devnet +ASC_CUSTOM_MINTABLE_TOKEN="" diff --git a/bridge-offchain-worker/README.md b/bridge/bridge-offchain-worker/README.md similarity index 99% rename from bridge-offchain-worker/README.md rename to bridge/bridge-offchain-worker/README.md index c80a2f4c..9c6c88f5 100644 --- a/bridge-offchain-worker/README.md +++ b/bridge/bridge-offchain-worker/README.md @@ -163,4 +163,4 @@ repayments truly happened. [setup]: ../hello-bridge/README.md#1-setup [Custom Contracts Bridging]: ../custom-contracts-bridging/README.md#33-update-environment-with-your-asc-contract-address [ASC Gitbook]: https://docs.attestcoin.org/ -[Loan Flow]: ../loan-flow/README.md +[Loan Flow]: ../../loan/scripts/README.md diff --git a/bridge-offchain-worker/worker.ts b/bridge/bridge-offchain-worker/worker.ts similarity index 98% rename from bridge-offchain-worker/worker.ts rename to bridge/bridge-offchain-worker/worker.ts index 75f5d09d..b2a24362 100644 --- a/bridge-offchain-worker/worker.ts +++ b/bridge/bridge-offchain-worker/worker.ts @@ -1,7 +1,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; -import burnerAbi from '../contracts/abi/TestERC20.json'; +import burnerAbi from '../../loan/contracts/abi/TestERC20.json'; import ASCMinterABI from '../contracts/abi/ASCMinter.json'; import { generateProofFor, @@ -12,7 +12,7 @@ import { POLLING_INTERVAL_MS, isValidContractAddress, isValidPrivateKey, -} from '../utils'; +} from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/contracts/DEPLOY.md b/bridge/contracts/DEPLOY.md similarity index 87% rename from contracts/DEPLOY.md rename to bridge/contracts/DEPLOY.md index 6c154f05..9c11372f 100644 --- a/contracts/DEPLOY.md +++ b/bridge/contracts/DEPLOY.md @@ -46,8 +46,8 @@ forge create \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ --gas-price \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: \ - contracts/sol/ASCMinter.sol:ASCMinter + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` **Solution 2: Wait and retry** (recommended) @@ -60,8 +60,8 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: \ - contracts/sol/ASCMinter.sol:ASCMinter + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` **Solution 3: Check current nonce and use next** @@ -76,8 +76,8 @@ forge create \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ --nonce X \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: \ - contracts/sol/ASCMinter.sol:ASCMinter + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` **Solution 4: Check if contract was already deployed** @@ -137,8 +137,8 @@ forge create \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ --nonce $((CURRENT_NONCE + 1)) \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: \ - contracts/sol/ASCMinter.sol:ASCMinter + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` ## Next Steps diff --git a/contracts/abi-creator.sh b/bridge/contracts/abi-creator.sh old mode 100755 new mode 100644 similarity index 68% rename from contracts/abi-creator.sh rename to bridge/contracts/abi-creator.sh index 488706ee..b3dcfb2d --- a/contracts/abi-creator.sh +++ b/bridge/contracts/abi-creator.sh @@ -1,15 +1,14 @@ #!/bin/bash -# WARNING: execute this from the root directory of this repository ! +# WARNING: execute this from the repository root. -sol_directory="contracts/sol" -abi_directory="contracts/abi" +sol_directory="bridge/contracts/sol" +abi_directory="bridge/contracts/abi" for p in "$sol_directory"/*; do file=$(basename "$p") contract_name="${file//.sol/}" file_with_extension="$contract_name.json" - # Extract only the ABI from the combined JSON output solc --base-path . --include-path "node_modules" \ "$sol_directory/$file" \ --combined-json abi --overwrite --json-indent 2 | \ diff --git a/contracts/abi/ASCBase.json b/bridge/contracts/abi/ASCBase.json similarity index 100% rename from contracts/abi/ASCBase.json rename to bridge/contracts/abi/ASCBase.json diff --git a/contracts/abi/ASCMinter.json b/bridge/contracts/abi/ASCMinter.json similarity index 100% rename from contracts/abi/ASCMinter.json rename to bridge/contracts/abi/ASCMinter.json diff --git a/contracts/abi/BridgeTestToken.json b/bridge/contracts/abi/BridgeTestToken.json similarity index 100% rename from contracts/abi/BridgeTestToken.json rename to bridge/contracts/abi/BridgeTestToken.json diff --git a/contracts/abi/MintableToken.json b/bridge/contracts/abi/MintableToken.json similarity index 100% rename from contracts/abi/MintableToken.json rename to bridge/contracts/abi/MintableToken.json diff --git a/contracts/abi/VerifierInterface.json b/bridge/contracts/abi/VerifierInterface.json similarity index 100% rename from contracts/abi/VerifierInterface.json rename to bridge/contracts/abi/VerifierInterface.json diff --git a/bridge/contracts/abi_generation.md b/bridge/contracts/abi_generation.md new file mode 100644 index 00000000..5b1d65a8 --- /dev/null +++ b/bridge/contracts/abi_generation.md @@ -0,0 +1,10 @@ +# ABI Generation + +After changing bridge contracts under `bridge/contracts/sol/`, regenerate ABIs from the repository root: + +```sh +yarn +./bridge/contracts/abi-creator.sh +``` + +For loan contracts, use `./loan/contracts/abi-creator.sh` instead. diff --git a/contracts/sol/ASCBase.sol b/bridge/contracts/sol/ASCBase.sol similarity index 100% rename from contracts/sol/ASCBase.sol rename to bridge/contracts/sol/ASCBase.sol diff --git a/contracts/sol/ASCMinter.sol b/bridge/contracts/sol/ASCMinter.sol similarity index 97% rename from contracts/sol/ASCMinter.sol rename to bridge/contracts/sol/ASCMinter.sol index 354e6b81..afa9784c 100644 --- a/contracts/sol/ASCMinter.sol +++ b/bridge/contracts/sol/ASCMinter.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import {ASCMintableToken, ASC_MINTER} from "./MintableToken.sol"; import {ASCBase} from "./ASCBase.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol"; contract ASCMinter is ASCBase { enum MinterActions { diff --git a/contracts/sol/BridgeTestToken.sol b/bridge/contracts/sol/BridgeTestToken.sol similarity index 100% rename from contracts/sol/BridgeTestToken.sol rename to bridge/contracts/sol/BridgeTestToken.sol diff --git a/contracts/sol/MintableToken.sol b/bridge/contracts/sol/MintableToken.sol similarity index 100% rename from contracts/sol/MintableToken.sol rename to bridge/contracts/sol/MintableToken.sol diff --git a/contracts/sol/VerifierInterface.sol b/bridge/contracts/sol/VerifierInterface.sol similarity index 100% rename from contracts/sol/VerifierInterface.sol rename to bridge/contracts/sol/VerifierInterface.sol diff --git a/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md similarity index 95% rename from custom-contracts-bridging/README.md rename to bridge/custom-contracts-bridging/README.md index d1907882..3fbd3934 100644 --- a/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -40,7 +40,7 @@ forge create \ --broadcast \ --rpc-url $SOURCE_CHAIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - contracts/sol/TestERC20.sol:TestERC20 + loan/contracts/sol/TestERC20.sol:TestERC20 ``` This should display some output containing the address of your test `ERC20` contract: @@ -88,7 +88,7 @@ of tokens which were burned on our _source chain_. > This is for demonstration purposes only, as bridging this way dilutes the value of our `TEST` > token each time we bridge it. -Start by opening the file `contracts/sol/ASCMinter.sol`. Next, navigate to the following line +Start by opening the file `bridge/contracts/sol/ASCMinter.sol`. Next, navigate to the following line inside of the `_processMint` function: ```sol @@ -112,7 +112,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder ``` You should get some output with the address of the library you just deployed: @@ -130,8 +130,8 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: \ - contracts/sol/ASCMinter.sol:ASCMinter + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` > [!IMPORTANT] @@ -172,7 +172,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - contracts/sol/BridgeTestToken.sol:BridgeTestToken \ + bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_CUSTOM_MINTER_CONTRACT_ADDRESS" ``` diff --git a/bridge/custom-contracts-bridging/submit_query.ts b/bridge/custom-contracts-bridging/submit_query.ts new file mode 100644 index 00000000..22402d15 --- /dev/null +++ b/bridge/custom-contracts-bridging/submit_query.ts @@ -0,0 +1,20 @@ +import { printBridgeSubmitUsage, runBridgeSubmitQuery } from '../../shared/submit_bridge_query'; + +async function main() { + const config = { + usageCommand: 'yarn custom_bridge:submit_query', + exampleTxHash: '0x87c97c776a678941b5941ec0cb602a4467ff4a35f77264208575f137cb05b2a7', + minterAddressEnvVar: 'ASC_CUSTOM_MINTER_CONTRACT_ADDRESS' as const, + }; + + const args = process.argv.slice(2); + if (args.length !== 1) { + printBridgeSubmitUsage(config); + process.exit(1); + } + + await runBridgeSubmitQuery(config, args[0]); + process.exit(0); +} + +main().catch(console.error); diff --git a/bridge/foundry.toml b/bridge/foundry.toml new file mode 100644 index 00000000..b98d3363 --- /dev/null +++ b/bridge/foundry.toml @@ -0,0 +1,13 @@ +[profile.default] +out = "out" +src = "contracts/sol" +libs = ["../../node_modules"] +remappings = [ + "@gluwa/usc-contracts/=../../usc-contracts/contracts/", +] +solc_version = "0.8.30" +optimizer = true +optimizer_runs = 200 +via_ir = false +evm_version = "shanghai" +verbosity = 2 diff --git a/hello-bridge/README.md b/bridge/hello-bridge/README.md similarity index 100% rename from hello-bridge/README.md rename to bridge/hello-bridge/README.md diff --git a/bridge/hello-bridge/submit_query.ts b/bridge/hello-bridge/submit_query.ts new file mode 100644 index 00000000..9e4c4f08 --- /dev/null +++ b/bridge/hello-bridge/submit_query.ts @@ -0,0 +1,20 @@ +import { printBridgeSubmitUsage, runBridgeSubmitQuery } from '../../shared/submit_bridge_query'; + +async function main() { + const config = { + usageCommand: 'yarn hello_bridge:submit_query', + exampleTxHash: '0x87c97c776a678941b5941ec0cb602a4467ff4a35f77264208575f137cb05b2a7', + minterAddressEnvVar: 'ASC_MINTER_CONTRACT_ADDRESS' as const, + }; + + const args = process.argv.slice(2); + if (args.length !== 1) { + printBridgeSubmitUsage(config); + process.exit(1); + } + + await runBridgeSubmitQuery(config, args[0]); + process.exit(0); +} + +main().catch(console.error); diff --git a/contracts/abi_generation.md b/contracts/abi_generation.md deleted file mode 100644 index f22dac3a..00000000 --- a/contracts/abi_generation.md +++ /dev/null @@ -1,16 +0,0 @@ -# ABI Generation - -In the case of any changes made to `TestERC20` you can replace the existing ABI with the results -generated here. - -1. From the root of the repository, first make sure that dependencies are installed: - -```sh -yarn -``` - -2. Then to generate the new ABI JSON from the root of the repository execute: - -```sh -./contracts/abi-creator.sh -``` diff --git a/foundry.toml b/foundry.toml index 79b45f03..8818a5f7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,8 +1,10 @@ [profile.default] out = "out" -src = "contracts" - +src = "bridge/contracts/sol" libs = ["node_modules"] +remappings = [ + "@gluwa/usc-contracts/=../usc-contracts/contracts/", +] solc_version = "0.8.30" optimizer = true optimizer_runs = 200 diff --git a/hello-bridge/submit_query.ts b/hello-bridge/submit_query.ts deleted file mode 100644 index a43aebc9..00000000 --- a/hello-bridge/submit_query.ts +++ /dev/null @@ -1,98 +0,0 @@ -import dotenv from 'dotenv'; -import { Contract, ethers, InterfaceAbi } from 'ethers'; - -import ASCMinterABI from '../contracts/abi/ASCMinter.json'; -import { - generateProofFor, - submitProofToMinterAndAwait, - computeGasLimitForMinter, - isValidPrivateKey, - isValidContractAddress, -} from '../utils'; - -dotenv.config({ override: true }); - -async function main() { - // Setup - const args = process.argv.slice(2); - - if (args.length !== 1) { - console.error(` - Usage: - yarn hello_bridge:submit_query - - Example: - yarn hello_bridge:submit_query 0x87c97c776a678941b5941ec0cb602a4467ff4a35f77264208575f137cb05b2a7 - `); - process.exit(1); - } - - const [transactionHash] = args; - - // Validate Transaction Hash - if (!transactionHash.startsWith('0x') || transactionHash.length !== 66) { - throw new Error('Invalid transaction hash provided'); - } - - // Validate Private Key - const ccNextPrivateKey = process.env.CREDITCOIN_WALLET_PRIVATE_KEY; - if (!isValidPrivateKey(ccNextPrivateKey)) { - throw new Error('CREDITCOIN_WALLET_PRIVATE_KEY environment variable is not configured or invalid'); - } - - const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); - if (!sourceChainKey) { - throw new Error('SOURCE_CHAIN_KEY environment variable is not configured or invalid'); - } - - const proverApiUrl = process.env.PROOF_BUILDER_URL; - if (!proverApiUrl) { - throw new Error('PROOF_BUILDER_URL is not configured or invalid'); - } - - const creditcoinRpcUrl = process.env.CREDITCOIN_RPC_URL; - if (!creditcoinRpcUrl) { - throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); - } - - const ascMinterContractAddress = process.env.ASC_MINTER_CONTRACT_ADDRESS; - if (!isValidContractAddress(ascMinterContractAddress)) { - throw new Error('ASC_MINTER_CONTRACT_ADDRESS is not configured or invalid'); - } - - const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; - if (!sourceChainRpcUrl) { - throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); - } - - // 1. Initialize RPC providers - const ccProvider = new ethers.JsonRpcProvider(creditcoinRpcUrl); - const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); - - // 2. Validate transaction and generate proof once the block is attested - const proofResult = await generateProofFor( - transactionHash, - sourceChainKey, - proverApiUrl, - ccProvider, - sourceChainProvider - ); - - // 3. Using previously generated proof, submit to ASC minter and await for the minted event - if (proofResult.success) { - // Establish link with the ASC contract - const wallet = new ethers.Wallet(ccNextPrivateKey!, ccProvider); - const contractABI = ASCMinterABI as unknown as InterfaceAbi; - const minterContract = new Contract(ascMinterContractAddress!, contractABI, wallet); - - const proofData = proofResult.data!; - const gasLimit = await computeGasLimitForMinter(ccProvider, minterContract, proofData, wallet.address); - await submitProofToMinterAndAwait(minterContract, proofData, gasLimit); - } else { - throw new Error(`Failed to generate proof: ${proofResult.error}`); - } - - process.exit(0); -} - -main().catch(console.error); diff --git a/loan/.env.example b/loan/.env.example new file mode 100644 index 00000000..2b7d9a9b --- /dev/null +++ b/loan/.env.example @@ -0,0 +1,25 @@ +# Loan example environment +# Copy to loan/.env or merge into the repository root .env + +## Shared network settings +SOURCE_CHAIN_KEY=8 +PROOF_BUILDER_URL="https://prover.usc-devnet.creditcoin.network/" +CREDITCOIN_RPC_URL="https://rpc.usc-devnet.creditcoin.network" +SOURCE_CHAIN_RPC_URL="" + +## User-deployed loan contracts +# ASCLoanManager on USC devnet +ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" +# AuxiliaryLoanContract on the source chain +SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" +# ERC20 used to fund/repay loans on the source chain +SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" + +## Role-specific wallets +# Lender account (registers loans, funds on source chain) +LENDER_WALLET_PRIVATE_KEY="" +# Borrower account (repays on source chain) +BORROWER_WALLET_PRIVATE_KEY="" + +## Optional: also used when scripts need a single execution-chain key +CREDITCOIN_WALLET_PRIVATE_KEY="" diff --git a/loan/contracts/abi-creator.sh b/loan/contracts/abi-creator.sh new file mode 100644 index 00000000..384c1eea --- /dev/null +++ b/loan/contracts/abi-creator.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# WARNING: execute this from the repository root. + +sol_directory="loan/contracts/sol" +abi_directory="loan/contracts/abi" + +for p in "$sol_directory"/*; do + file=$(basename "$p") + contract_name="${file//.sol/}" + file_with_extension="$contract_name.json" + solc --base-path . --include-path "node_modules" \ + --allow-paths "bridge/contracts/sol" \ + "$sol_directory/$file" \ + --combined-json abi --overwrite --json-indent 2 | \ + jq ".contracts[\"$p:$contract_name\"].abi // {}" > "$abi_directory/$file_with_extension" +done diff --git a/contracts/abi/ASCLoanManager.json b/loan/contracts/abi/ASCLoanManager.json similarity index 100% rename from contracts/abi/ASCLoanManager.json rename to loan/contracts/abi/ASCLoanManager.json diff --git a/contracts/abi/AuxiliaryLoanContract.json b/loan/contracts/abi/AuxiliaryLoanContract.json similarity index 100% rename from contracts/abi/AuxiliaryLoanContract.json rename to loan/contracts/abi/AuxiliaryLoanContract.json diff --git a/contracts/abi/LoanTypes.json b/loan/contracts/abi/LoanTypes.json similarity index 100% rename from contracts/abi/LoanTypes.json rename to loan/contracts/abi/LoanTypes.json diff --git a/contracts/abi/TestERC20.json b/loan/contracts/abi/TestERC20.json similarity index 100% rename from contracts/abi/TestERC20.json rename to loan/contracts/abi/TestERC20.json diff --git a/loan/contracts/abi_generation.md b/loan/contracts/abi_generation.md new file mode 100644 index 00000000..00e3d7ec --- /dev/null +++ b/loan/contracts/abi_generation.md @@ -0,0 +1,10 @@ +# ABI Generation + +After changing loan contracts under `loan/contracts/sol/`, regenerate ABIs from the repository root: + +```sh +yarn +./loan/contracts/abi-creator.sh +``` + +Loan contracts import `ASCBase` and decoding libraries from `@gluwa/usc-contracts` (see `loan/foundry.toml` remappings). diff --git a/contracts/sol/ASCLoanManager.sol b/loan/contracts/sol/ASCLoanManager.sol similarity index 97% rename from contracts/sol/ASCLoanManager.sol rename to loan/contracts/sol/ASCLoanManager.sol index 58c4a271..dc09655f 100644 --- a/contracts/sol/ASCLoanManager.sol +++ b/loan/contracts/sol/ASCLoanManager.sol @@ -9,9 +9,8 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {LoanFlow, LoanStatus, LoanOrder, LoanTerms} from "./LoanTypes.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; -import {NativeQueryVerifierLib} from "./VerifierInterface.sol"; -import {ASCBase} from "./ASCBase.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol"; +import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; /** * @title ASCLoanManager @@ -62,8 +61,6 @@ contract ASCLoanManager is Ownable, ReentrancyGuard, ASCBase { constructor() Ownable(msg.sender) { nextLoanId = 1; - // Get the precompile instance using the helper library - VERIFIER = NativeQueryVerifierLib.getVerifier(); } /** diff --git a/contracts/sol/AuxiliaryLoanContract.sol b/loan/contracts/sol/AuxiliaryLoanContract.sol similarity index 100% rename from contracts/sol/AuxiliaryLoanContract.sol rename to loan/contracts/sol/AuxiliaryLoanContract.sol diff --git a/contracts/sol/LoanTypes.sol b/loan/contracts/sol/LoanTypes.sol similarity index 100% rename from contracts/sol/LoanTypes.sol rename to loan/contracts/sol/LoanTypes.sol diff --git a/contracts/sol/TestERC20.sol b/loan/contracts/sol/TestERC20.sol similarity index 100% rename from contracts/sol/TestERC20.sol rename to loan/contracts/sol/TestERC20.sol diff --git a/loan/foundry.toml b/loan/foundry.toml new file mode 100644 index 00000000..9e5db51f --- /dev/null +++ b/loan/foundry.toml @@ -0,0 +1,14 @@ +[profile.default] +out = "out" +src = "contracts/sol" +libs = ["../../node_modules"] +remappings = [ + "@openzeppelin/=../../node_modules/@openzeppelin/", + "@gluwa/usc-contracts/=../../usc-contracts/contracts/", +] +solc_version = "0.8.30" +optimizer = true +optimizer_runs = 200 +via_ir = false +evm_version = "shanghai" +verbosity = 2 diff --git a/loan-flow/README.md b/loan/scripts/README.md similarity index 96% rename from loan-flow/README.md rename to loan/scripts/README.md index 8739fb60..cad4a3a6 100644 --- a/loan-flow/README.md +++ b/loan/scripts/README.md @@ -50,7 +50,7 @@ forge create \ --broadcast \ --rpc-url $SOURCE_CHAIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - contracts/sol/TestERC20.sol:TestERC20 + loan/contracts/sol/TestERC20.sol:TestERC20 ``` This should display some output containing the address of your test `ERC20` contract: @@ -75,7 +75,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder ``` You should get some output with the address of the library you just deployed: @@ -93,8 +93,8 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: \ - contracts/sol/ASCLoanManager.sol:ASCLoanManager + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager ``` > [!IMPORTANT] @@ -113,7 +113,7 @@ forge create \ --broadcast \ --rpc-url $SOURCE_CHAIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract + loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract ``` Grab the address again (it gets repetitive huh?) and add it to the `.env` file, like so: @@ -499,7 +499,7 @@ You've learned: If you haven't already, take a look at the [ASC Gitbook] for more information. -[Bridge Offchain Worker]: ../bridge-offchain-worker/README.md -[Hello Bridge]: ../hello-bridge/README.md#11-generate-a-new-wallet-address +[Bridge Offchain Worker]: ../../bridge/bridge-offchain-worker/README.md +[Hello Bridge]: ../../bridge/hello-bridge/README.md#11-generate-a-new-wallet-address [ASC Gitbook]: https://docs.attestcoin.org/ [๐Ÿšฐ testnet faucet]: https://cloud.google.com/application/web3/faucet/ethereum/sepolia diff --git a/loan-flow/authorize.ts b/loan/scripts/authorize.ts similarity index 96% rename from loan-flow/authorize.ts rename to loan/scripts/authorize.ts index ea1f9cb7..0523f5c4 100644 --- a/loan-flow/authorize.ts +++ b/loan/scripts/authorize.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; -import { isValidContractAddress, isValidPrivateKey } from '../utils'; +import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan-flow/fund_loan.ts b/loan/scripts/fund_loan.ts similarity index 98% rename from loan-flow/fund_loan.ts rename to loan/scripts/fund_loan.ts index 13316331..42aceb92 100644 --- a/loan-flow/fund_loan.ts +++ b/loan/scripts/fund_loan.ts @@ -3,7 +3,7 @@ import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import ERC20Abi from '../contracts/abi/TestERC20.json'; -import { isValidContractAddress, isValidPrivateKey } from '../utils'; +import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan-flow/inspect.ts b/loan/scripts/inspect.ts similarity index 98% rename from loan-flow/inspect.ts rename to loan/scripts/inspect.ts index 5310c21e..0185539c 100644 --- a/loan-flow/inspect.ts +++ b/loan/scripts/inspect.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; -import { isValidContractAddress } from '../utils'; +import { isValidContractAddress } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan-flow/register.ts b/loan/scripts/register.ts similarity index 98% rename from loan-flow/register.ts rename to loan/scripts/register.ts index d6ac8a69..85358578 100644 --- a/loan-flow/register.ts +++ b/loan/scripts/register.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; import { Contract, ethers, EventLog } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; -import { isValidContractAddress, isValidPrivateKey } from '../utils'; +import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan-flow/register_source_contract.ts b/loan/scripts/register_source_contract.ts similarity index 97% rename from loan-flow/register_source_contract.ts rename to loan/scripts/register_source_contract.ts index 7f4016e6..d0f1dbfd 100644 --- a/loan-flow/register_source_contract.ts +++ b/loan/scripts/register_source_contract.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; -import { isValidContractAddress, isValidPrivateKey } from '../utils'; +import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan-flow/repay_loan.ts b/loan/scripts/repay_loan.ts similarity index 98% rename from loan-flow/repay_loan.ts rename to loan/scripts/repay_loan.ts index 40210109..0f7fedfa 100644 --- a/loan-flow/repay_loan.ts +++ b/loan/scripts/repay_loan.ts @@ -3,7 +3,7 @@ import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import ERC20Abi from '../contracts/abi/TestERC20.json'; -import { isValidContractAddress, isValidPrivateKey } from '../utils'; +import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan-flow/worker.ts b/loan/scripts/worker.ts similarity index 99% rename from loan-flow/worker.ts rename to loan/scripts/worker.ts index 092f43eb..7569a282 100644 --- a/loan-flow/worker.ts +++ b/loan/scripts/worker.ts @@ -13,7 +13,7 @@ import { POLLING_INTERVAL_MS, submitFundProofToLoanManager, submitRepayProofToLoanManager, -} from '../utils'; +} from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..12303dcc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2139 @@ +{ + "name": "asc-testnet-bridge-examples", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "asc-testnet-bridge-examples", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "@gluwa/usc-contracts": "0.1.2", + "@gluwa/usc-sdk": "0.18.0", + "@openzeppelin/contracts": "5.4.0", + "@types/node": "26.1.0", + "dotenv": "17.4.2", + "ethers": "^6.17.0" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.9.4", + "tsx": "^4.22.4", + "typescript": "6.0.2" + } + }, + "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==" + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@gluwa/usc-contracts": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gluwa/usc-contracts/-/usc-contracts-0.1.2.tgz", + "integrity": "sha512-6GbBApjqhb6uWGzo6mLOZxH2RNq8iCOoRuYXn97gIQIaIHMk/pjQdDk2vJolX11TnhQbcrtoE/em/RDQyrJJdQ==" + }, + "node_modules/@gluwa/usc-sdk": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@gluwa/usc-sdk/-/usc-sdk-0.18.0.tgz", + "integrity": "sha512-iUEXPAp1gB/HDYkXad+StVJvs0Yz2ZIbzijc8SaBflGJ95hPnVahyY+tsP2PApswSQaX2uIBM7Cb0jpZ+KBzTQ==", + "dependencies": { + "axios": "^1.13.2", + "dotenv": "^17.2.3", + "ethers": "^6.15.0", + "exponential-backoff": "^3.1.3" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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/@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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@openzeppelin/contracts": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", + "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.62.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.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/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "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/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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/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/esbuild": { + "version": "0.28.0", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ethers": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.17.0.tgz", + "integrity": "sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.11.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.21.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/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/ethers/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/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "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/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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/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-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/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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/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", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "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/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "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": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index a80e0a1c..7f6968f0 100644 --- a/package.json +++ b/package.json @@ -3,21 +3,21 @@ "name": "asc-testnet-bridge-examples", "version": "0.2.0", "scripts": { - "hello_bridge:submit_query": "tsx hello-bridge/submit_query.ts", - "custom_bridge:submit_query": "tsx custom-contracts-bridging/submit_query.ts", - "offchain:start_worker": "tsx bridge-offchain-worker/worker.ts", - "loan_flow:authorize_token": "tsx loan-flow/authorize.ts", - "loan_flow:register_source_contract": "tsx loan-flow/register_source_contract.ts", - "loan_flow:register_loan": "tsx loan-flow/register.ts", - "loan_flow:fund_loan": "tsx loan-flow/fund_loan.ts", - "loan_flow:repay_loan": "tsx loan-flow/repay_loan.ts", - "loan_flow:inspect_loan": "tsx loan-flow/inspect.ts", - "loan_flow:start_worker": "tsx loan-flow/worker.ts", - "utils:check_balance": "node utils/check_balance.js", + "hello_bridge:submit_query": "tsx bridge/hello-bridge/submit_query.ts", + "custom_bridge:submit_query": "tsx bridge/custom-contracts-bridging/submit_query.ts", + "offchain:start_worker": "tsx bridge/bridge-offchain-worker/worker.ts", + "loan_flow:authorize_token": "tsx loan/scripts/authorize.ts", + "loan_flow:register_source_contract": "tsx loan/scripts/register_source_contract.ts", + "loan_flow:register_loan": "tsx loan/scripts/register.ts", + "loan_flow:fund_loan": "tsx loan/scripts/fund_loan.ts", + "loan_flow:repay_loan": "tsx loan/scripts/repay_loan.ts", + "loan_flow:inspect_loan": "tsx loan/scripts/inspect.ts", + "loan_flow:start_worker": "tsx loan/scripts/worker.ts", + "utils:check_balance": "node shared/utils/check_balance.js", "check-format": "prettier --check .", "eslint": "eslint -c .eslintrc.js --ignore-pattern .eslintrc.js --ignore-pattern check_balance.js --max-warnings 0 --ext .ts --ext .js .", "format": "prettier --write .", - "build": "forge build", + "build": "forge build --root bridge && forge build --root loan", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/custom-contracts-bridging/submit_query.ts b/shared/submit_bridge_query.ts similarity index 55% rename from custom-contracts-bridging/submit_query.ts rename to shared/submit_bridge_query.ts index a4356600..f310ed5e 100644 --- a/custom-contracts-bridging/submit_query.ts +++ b/shared/submit_bridge_query.ts @@ -1,97 +1,91 @@ -import dotenv from 'dotenv'; -import { Contract, ethers, InterfaceAbi } from 'ethers'; - -import ASCMinterABI from '../contracts/abi/ASCMinter.json'; -import { - generateProofFor, - computeGasLimitForMinter, - submitProofToMinterAndAwait, - isValidPrivateKey, - isValidContractAddress, -} from '../utils'; - -dotenv.config({ override: true }); - -async function main() { - // Setup - const args = process.argv.slice(2); - - if (args.length !== 1) { - console.error(` - Usage: - yarn custom_bridge:submit_query - - Example: - yarn custom_bridge:submit_query 0x87c97c776a678941b5941ec0cb602a4467ff4a35f77264208575f137cb05b2a7 - `); - process.exit(1); - } - - const [transactionHash] = args; - - // Validate Transaction Hash - if (!transactionHash.startsWith('0x') || transactionHash.length !== 66) { - throw new Error('Invalid transaction hash provided'); - } - - // Validate Private Key - const ccNextPrivateKey = process.env.CREDITCOIN_WALLET_PRIVATE_KEY; - if (!isValidPrivateKey(ccNextPrivateKey)) { - throw new Error('CREDITCOIN_WALLET_PRIVATE_KEY environment variable is not configured or invalid'); - } - - const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); - if (!sourceChainKey) { - throw new Error('SOURCE_CHAIN_KEY environment variable is not configured or invalid'); - } - - const proofBuilderUrl = process.env.PROOF_BUILDER_URL; - if (!proofBuilderUrl) { - throw new Error('PROOF_BUILDER_URL is not configured or invalid'); - } - - const creditcoinRpcUrl = process.env.CREDITCOIN_RPC_URL; - if (!creditcoinRpcUrl) { - throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); - } - - const minterContractAddress = process.env.ASC_CUSTOM_MINTER_CONTRACT_ADDRESS; - if (!isValidContractAddress(minterContractAddress)) { - throw new Error('ASC_CUSTOM_MINTER_CONTRACT_ADDRESS is not configured or invalid'); - } - const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; - if (!sourceChainRpcUrl) { - throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); - } - - // 1. Initialize RPC providers - const ccProvider = new ethers.JsonRpcProvider(creditcoinRpcUrl); - const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); - - // 2. Validate transaction and generate proof once the block is attested - const proofResult = await generateProofFor( - transactionHash, - sourceChainKey, - proofBuilderUrl, - ccProvider, - sourceChainProvider - ); - - // 3. Using previously generated proof, submit to ASC minter and await for the minted event - if (proofResult.success) { - // Establish link with the ASC contract - const wallet = new ethers.Wallet(ccNextPrivateKey!, ccProvider); - const contractABI = ASCMinterABI as unknown as InterfaceAbi; - const minterContract = new Contract(minterContractAddress!, contractABI, wallet); - - const proofData = proofResult.data!; - const gasLimit = await computeGasLimitForMinter(ccProvider, minterContract, proofData, wallet.address); - await submitProofToMinterAndAwait(minterContract, proofData, gasLimit); - } else { - throw new Error(`Failed to generate proof: ${proofResult.error}`); - } - - process.exit(0); -} - -main().catch(console.error); +import dotenv from 'dotenv'; +import { Contract, ethers, InterfaceAbi } from 'ethers'; + +import ASCMinterABI from '../bridge/contracts/abi/ASCMinter.json'; +import { + generateProofFor, + submitProofToMinterAndAwait, + computeGasLimitForMinter, + isValidPrivateKey, + isValidContractAddress, +} from './utils'; + +dotenv.config({ override: true }); + +export type BridgeSubmitConfig = { + usageCommand: string; + exampleTxHash: string; + minterAddressEnvVar: 'ASC_MINTER_CONTRACT_ADDRESS' | 'ASC_CUSTOM_MINTER_CONTRACT_ADDRESS'; +}; + +export function printBridgeSubmitUsage(config: BridgeSubmitConfig): void { + console.error(` + Usage: + ${config.usageCommand} + + Example: + ${config.usageCommand} ${config.exampleTxHash} + `); +} + +export async function runBridgeSubmitQuery( + config: BridgeSubmitConfig, + transactionHash: string +): Promise { + if (!transactionHash.startsWith('0x') || transactionHash.length !== 66) { + throw new Error('Invalid transaction hash provided'); + } + + const ccNextPrivateKey = process.env.CREDITCOIN_WALLET_PRIVATE_KEY; + if (!isValidPrivateKey(ccNextPrivateKey)) { + throw new Error('CREDITCOIN_WALLET_PRIVATE_KEY environment variable is not configured or invalid'); + } + + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + if (!sourceChainKey) { + throw new Error('SOURCE_CHAIN_KEY environment variable is not configured or invalid'); + } + + const proofBuilderUrl = process.env.PROOF_BUILDER_URL; + if (!proofBuilderUrl) { + throw new Error('PROOF_BUILDER_URL is not configured or invalid'); + } + + const creditcoinRpcUrl = process.env.CREDITCOIN_RPC_URL; + if (!creditcoinRpcUrl) { + throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); + } + + const minterContractAddress = process.env[config.minterAddressEnvVar]; + if (!isValidContractAddress(minterContractAddress)) { + throw new Error(`${config.minterAddressEnvVar} is not configured or invalid`); + } + + const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; + if (!sourceChainRpcUrl) { + throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); + } + + const ccProvider = new ethers.JsonRpcProvider(creditcoinRpcUrl); + const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); + + const proofResult = await generateProofFor( + transactionHash, + sourceChainKey, + proofBuilderUrl, + ccProvider, + sourceChainProvider + ); + + if (!proofResult.success) { + throw new Error(`Failed to generate proof: ${proofResult.error}`); + } + + const wallet = new ethers.Wallet(ccNextPrivateKey!, ccProvider); + const contractABI = ASCMinterABI as unknown as InterfaceAbi; + const minterContract = new Contract(minterContractAddress!, contractABI, wallet); + + const proofData = proofResult.data!; + const gasLimit = await computeGasLimitForMinter(ccProvider, minterContract, proofData, wallet.address); + await submitProofToMinterAndAwait(minterContract, proofData, gasLimit); +} diff --git a/utils/check_balance.js b/shared/utils/check_balance.js similarity index 100% rename from utils/check_balance.js rename to shared/utils/check_balance.js diff --git a/utils/index.ts b/shared/utils/index.ts similarity index 100% rename from utils/index.ts rename to shared/utils/index.ts diff --git a/yarn.lock b/yarn.lock index 700ad763..a17daf3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,154 +4,27 @@ "@adraffy/ens-normalize@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz#6c2d657d4b2dfb37f8ea811dcb3e60843d4ac24a" + resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz" integrity sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ== -"@esbuild/aix-ppc64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz#7a01a8d2ec2fbb2dac78adad09b0fa781e4082be" - integrity sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ== - -"@esbuild/android-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz#b540a27d14e4afd058496a4dbec4d3f414db110a" - integrity sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg== - -"@esbuild/android-arm@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz#704bd297de6d762de54eabbeafbf55f6756abe2f" - integrity sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ== - -"@esbuild/android-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz#d1cb166d34b0fbf0fe8ab460a5594f24a378701e" - integrity sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng== - -"@esbuild/darwin-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz#1034b26457fc886368fe61bbd09f653f6afa8e54" - integrity sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q== - -"@esbuild/darwin-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz#65556a432a1e4d72032d8218c1932fcca1a49772" - integrity sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ== - -"@esbuild/freebsd-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz#2e61e0592f9030d7e3dae18ee25ebc535918aef6" - integrity sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw== - -"@esbuild/freebsd-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz#c95ec289959ef8079c4dca817a1e2c4be66b9bd3" - integrity sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ== - -"@esbuild/linux-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz#40b22175dda06182f3ee8141186c5ff304c4a717" - integrity sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g== - -"@esbuild/linux-arm@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz#c09a0f67917592ac0de892a9be4d3814debd2a6c" - integrity sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ== - -"@esbuild/linux-ia32@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz#a580f9c676797833891e519fc7a1337c8afd8db3" - integrity sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w== - -"@esbuild/linux-loong64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz#46452cf321dc7f9e91c2fa780a56bb56e79cd68b" - integrity sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg== - -"@esbuild/linux-mips64el@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz#4211b3184dd6608f53dcb22e39f5d34ee08852c8" - integrity sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ== - -"@esbuild/linux-ppc64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz#697857c2a61cb9b0b6bb6652e40c1dc5e1ca8e5d" - integrity sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ== - -"@esbuild/linux-riscv64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz#d192943eb146a40ac4c6497d0cf7be35b986bf08" - integrity sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ== - -"@esbuild/linux-s390x@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz#acea0356da0e0ebc08f97cf7b9c2e401e1e648dc" - integrity sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag== - -"@esbuild/linux-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz#6f0c3ce0cb64c534b70c4c45ecb2c16d34e35dfd" - integrity sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA== - -"@esbuild/netbsd-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz#8bcd77077a0dce3378b574fedb26d2a253b73d36" - integrity sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw== - -"@esbuild/netbsd-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz#e7fb2a01e99c830c94e6623cd9fefb4c8fb58347" - integrity sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg== - -"@esbuild/openbsd-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz#c52909372db8b86e2c55e05a8940033b5660a3b2" - integrity sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q== - -"@esbuild/openbsd-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz#c427b9be5a64c262ff9a7eb70b5fbbaadf446c6c" - integrity sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw== - -"@esbuild/openharmony-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz#dc9b147baca2e6c4b3c85571741ef4860a489097" - integrity sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg== - -"@esbuild/sunos-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz#ce866d12df13c15e4c99f073a3d466f6e0649b3a" - integrity sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ== - -"@esbuild/win32-arm64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz#7468e3692d01d629d5941e5d83817bb80f9e39b4" - integrity sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA== - -"@esbuild/win32-ia32@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz#a5bc0063fb2bcab6d0ed63f2a1537958bc269ec6" - integrity sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg== - -"@esbuild/win32-x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz#10064ee44f4347b90c9a02b446bbf80a91632b12" - integrity sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A== +"@esbuild/win32-x64@0.28.0": + version "0.28.0" "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.12.2", "@eslint-community/regexpp@^4.6.1": version "4.12.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== "@eslint/eslintrc@^2.1.4": version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" @@ -166,17 +39,17 @@ "@eslint/js@8.57.1": version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== "@gluwa/usc-contracts@0.1.2": version "0.1.2" - resolved "https://registry.yarnpkg.com/@gluwa/usc-contracts/-/usc-contracts-0.1.2.tgz#480ba30d2eb6f2b96f884ee6f20963c677b94d64" + resolved "https://registry.npmjs.org/@gluwa/usc-contracts/-/usc-contracts-0.1.2.tgz" integrity sha512-6GbBApjqhb6uWGzo6mLOZxH2RNq8iCOoRuYXn97gIQIaIHMk/pjQdDk2vJolX11TnhQbcrtoE/em/RDQyrJJdQ== "@gluwa/usc-sdk@0.18.0": version "0.18.0" - resolved "https://registry.yarnpkg.com/@gluwa/usc-sdk/-/usc-sdk-0.18.0.tgz#03d0bbcd00a9a9a6b91eb4e9565fc861ee8978da" + resolved "https://registry.npmjs.org/@gluwa/usc-sdk/-/usc-sdk-0.18.0.tgz" integrity sha512-iUEXPAp1gB/HDYkXad+StVJvs0Yz2ZIbzijc8SaBflGJ95hPnVahyY+tsP2PApswSQaX2uIBM7Cb0jpZ+KBzTQ== dependencies: axios "^1.13.2" @@ -186,7 +59,7 @@ "@humanwhocodes/config-array@^0.13.0": version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz" integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== dependencies: "@humanwhocodes/object-schema" "^2.0.3" @@ -195,29 +68,29 @@ "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@noble/curves@1.2.0": version "1.2.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz" integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== dependencies: "@noble/hashes" "1.3.2" "@noble/hashes@1.3.2": version "1.3.2" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -225,12 +98,12 @@ "@nodelib/fs.stat@2.0.5": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -238,26 +111,26 @@ "@openzeppelin/contracts@5.4.0": version "5.4.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.4.0.tgz#177594bdb2d86c71f5d1052fe40cb4edb95fb20f" + resolved "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz" integrity sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A== "@types/node@22.7.5": version "22.7.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + resolved "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz" integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== dependencies: undici-types "~6.19.2" "@types/node@26.1.0": version "26.1.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.0.tgz#aa85f0727fc5611347091c478341c63650903439" + resolved "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz" integrity sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw== dependencies: undici-types "~8.3.0" "@typescript-eslint/eslint-plugin@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz#1736dcdca6cae3359d818456a47d18b674761f7f" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz" integrity sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA== dependencies: "@eslint-community/regexpp" "^4.12.2" @@ -269,9 +142,9 @@ natural-compare "^1.4.0" ts-api-utils "^2.5.0" -"@typescript-eslint/parser@8.62.1": +"@typescript-eslint/parser@^8.62.1", "@typescript-eslint/parser@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.62.1.tgz#d3f7ba18f1bf78bfb7256fea021d1927b48e7080" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz" integrity sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA== dependencies: "@typescript-eslint/scope-manager" "8.62.1" @@ -282,7 +155,7 @@ "@typescript-eslint/project-service@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.62.1.tgz#78d880eb1cf6859b5ec263d04f95403e9f90ae47" + resolved "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz" integrity sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg== dependencies: "@typescript-eslint/tsconfig-utils" "^8.62.1" @@ -291,20 +164,20 @@ "@typescript-eslint/scope-manager@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz#7ee65e9a6eb3ccdc4816593a4ff38840306de88a" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz" integrity sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg== dependencies: "@typescript-eslint/types" "8.62.1" "@typescript-eslint/visitor-keys" "8.62.1" -"@typescript-eslint/tsconfig-utils@8.62.1", "@typescript-eslint/tsconfig-utils@^8.62.1": +"@typescript-eslint/tsconfig-utils@^8.62.1", "@typescript-eslint/tsconfig-utils@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz#e2b5f24fe721044189cb7e81117c96d75979d627" + resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz" integrity sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g== "@typescript-eslint/type-utils@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz#ebd30b13bacb13070917259a23309cf644121f9a" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz" integrity sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg== dependencies: "@typescript-eslint/types" "8.62.1" @@ -313,14 +186,14 @@ debug "^4.4.3" ts-api-utils "^2.5.0" -"@typescript-eslint/types@8.62.1", "@typescript-eslint/types@^8.62.1": +"@typescript-eslint/types@^8.62.1", "@typescript-eslint/types@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.62.1.tgz#c58be954e483b2fc98275374d5bcb40b99842dc1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz" integrity sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q== "@typescript-eslint/typescript-estree@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz#98c1bb17635d5b026b24193a8d29188ac64380ff" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz" integrity sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA== dependencies: "@typescript-eslint/project-service" "8.62.1" @@ -335,7 +208,7 @@ "@typescript-eslint/utils@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.62.1.tgz#1622b75c7e6df308181dd0b44855dc4228da0457" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz" integrity sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g== dependencies: "@eslint-community/eslint-utils" "^4.9.1" @@ -345,7 +218,7 @@ "@typescript-eslint/visitor-keys@8.62.1": version "8.62.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz#499657d77ffafb8a99eb1d6c97847ca430234722" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz" integrity sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g== dependencies: "@typescript-eslint/types" "8.62.1" @@ -353,27 +226,27 @@ "@ungap/structured-clone@^1.2.0": version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.9.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== aes-js@4.0.0-beta.5: version "4.0.0-beta.5" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + resolved "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz" integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -383,29 +256,29 @@ ajv@^6.12.4: ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== axios@^1.13.2: version "1.16.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.0.tgz#f8e5dd931cef2a5f8c32216d5784eda2f8750eb7" + resolved "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz" integrity sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w== dependencies: follow-redirects "^1.16.0" @@ -414,32 +287,32 @@ axios@^1.13.2: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== balanced-match@^4.0.2: version "4.0.4" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== brace-expansion@^1.1.7: version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^5.0.2: - version "5.0.4" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" - integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== +brace-expansion@^5.0.8: + version "5.0.9" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz" + integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== dependencies: balanced-match "^4.0.2" call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" @@ -447,12 +320,12 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== chalk@^4.0.0: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -460,31 +333,31 @@ chalk@^4.0.0: color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== cross-spawn@^7.0.2: version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" @@ -493,36 +366,36 @@ cross-spawn@^7.0.2: debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" -dotenv@17.4.2, dotenv@^17.2.3: +dotenv@^17.2.3, dotenv@17.4.2: version "17.4.2" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.4.2.tgz#c07e54a746e11eba021dd9e1047ced5afdc1c034" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz" integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw== dunder-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: call-bind-apply-helpers "^1.0.1" @@ -531,24 +404,24 @@ dunder-proto@^1.0.1: es-define-property@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" es-set-tostringtag@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== dependencies: es-errors "^1.3.0" @@ -557,50 +430,48 @@ es-set-tostringtag@^2.1.0: hasown "^2.0.2" esbuild@~0.28.0: - version "0.28.1" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.1.tgz#ef45b4634c9c9d97a296aea4114a5f9840f95578" - integrity sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw== + version "0.28.0" optionalDependencies: - "@esbuild/aix-ppc64" "0.28.1" - "@esbuild/android-arm" "0.28.1" - "@esbuild/android-arm64" "0.28.1" - "@esbuild/android-x64" "0.28.1" - "@esbuild/darwin-arm64" "0.28.1" - "@esbuild/darwin-x64" "0.28.1" - "@esbuild/freebsd-arm64" "0.28.1" - "@esbuild/freebsd-x64" "0.28.1" - "@esbuild/linux-arm" "0.28.1" - "@esbuild/linux-arm64" "0.28.1" - "@esbuild/linux-ia32" "0.28.1" - "@esbuild/linux-loong64" "0.28.1" - "@esbuild/linux-mips64el" "0.28.1" - "@esbuild/linux-ppc64" "0.28.1" - "@esbuild/linux-riscv64" "0.28.1" - "@esbuild/linux-s390x" "0.28.1" - "@esbuild/linux-x64" "0.28.1" - "@esbuild/netbsd-arm64" "0.28.1" - "@esbuild/netbsd-x64" "0.28.1" - "@esbuild/openbsd-arm64" "0.28.1" - "@esbuild/openbsd-x64" "0.28.1" - "@esbuild/openharmony-arm64" "0.28.1" - "@esbuild/sunos-x64" "0.28.1" - "@esbuild/win32-arm64" "0.28.1" - "@esbuild/win32-ia32" "0.28.1" - "@esbuild/win32-x64" "0.28.1" + "@esbuild/aix-ppc64" "0.28.0" + "@esbuild/android-arm" "0.28.0" + "@esbuild/android-arm64" "0.28.0" + "@esbuild/android-x64" "0.28.0" + "@esbuild/darwin-arm64" "0.28.0" + "@esbuild/darwin-x64" "0.28.0" + "@esbuild/freebsd-arm64" "0.28.0" + "@esbuild/freebsd-x64" "0.28.0" + "@esbuild/linux-arm" "0.28.0" + "@esbuild/linux-arm64" "0.28.0" + "@esbuild/linux-ia32" "0.28.0" + "@esbuild/linux-loong64" "0.28.0" + "@esbuild/linux-mips64el" "0.28.0" + "@esbuild/linux-ppc64" "0.28.0" + "@esbuild/linux-riscv64" "0.28.0" + "@esbuild/linux-s390x" "0.28.0" + "@esbuild/linux-x64" "0.28.0" + "@esbuild/netbsd-arm64" "0.28.0" + "@esbuild/netbsd-x64" "0.28.0" + "@esbuild/openbsd-arm64" "0.28.0" + "@esbuild/openbsd-x64" "0.28.0" + "@esbuild/openharmony-arm64" "0.28.0" + "@esbuild/sunos-x64" "0.28.0" + "@esbuild/win32-arm64" "0.28.0" + "@esbuild/win32-ia32" "0.28.0" + "@esbuild/win32-x64" "0.28.0" escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^10.1.8: version "10.1.8" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz" integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== eslint-scope@^7.2.2: version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" @@ -608,17 +479,17 @@ eslint-scope@^7.2.2: eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint-visitor-keys@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@^8.57.0: +"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.57.0, "eslint@^8.57.0 || ^9.0.0 || ^10.0.0", eslint@>=7.0.0: version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" @@ -662,7 +533,7 @@ eslint@^8.57.0: espree@^9.6.0, espree@^9.6.1: version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: acorn "^8.9.0" @@ -671,31 +542,31 @@ espree@^9.6.0, espree@^9.6.1: esquery@^1.4.2: version "1.7.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz" integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== ethers@^6.15.0, ethers@^6.17.0: version "6.17.0" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.17.0.tgz#ad85ee1fc45fe2d95cb1e21c4e3ff9f7f1b093cc" + resolved "https://registry.npmjs.org/ethers/-/ethers-6.17.0.tgz" integrity sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang== dependencies: "@adraffy/ens-normalize" "1.11.1" @@ -708,46 +579,46 @@ ethers@^6.15.0, ethers@^6.17.0: exponential-backoff@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6" + resolved "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz" integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: version "1.20.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz" integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" fdir@^6.5.0: version "6.5.0" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -755,7 +626,7 @@ find-up@^5.0.0: flat-cache@^3.0.4: version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz" integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: flatted "^3.2.9" @@ -764,43 +635,36 @@ flat-cache@^3.0.4: flatted@^3.2.9: version "3.4.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== follow-redirects@^1.16.0: version "1.16.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz" integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== form-data@^4.0.5: - version "4.0.6" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827" - integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ== + version "4.0.5" dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" es-set-tostringtag "^2.1.0" - hasown "^2.0.4" - mime-types "^2.1.35" + hasown "^2.0.2" + mime-types "^2.1.12" fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== get-intrinsic@^1.2.6: version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -816,7 +680,7 @@ get-intrinsic@^1.2.6: get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" @@ -824,14 +688,14 @@ get-proto@^1.0.1: glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" glob@^7.1.3: version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -843,65 +707,58 @@ glob@^7.1.3: globals@^13.19.0: version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" gopd@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== graphemer@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" hasown@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" -hasown@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" - integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== - dependencies: - function-bind "^1.1.2" - ignore@^5.2.0: version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== ignore@^7.0.5: version "7.0.5" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== import-fresh@^3.2.1: version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" @@ -909,12 +766,12 @@ import-fresh@^3.2.1: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -922,63 +779,61 @@ inflight@^1.0.4: inherits@2: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-glob@^4.0.0, is-glob@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-path-inside@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== js-yaml@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.0.tgz#d1900572a7f7cf0b5f540c83673e60bad3436592" - integrity sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q== + version "4.1.1" dependencies: argparse "^2.0.1" json-buffer@3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== keyv@^4.5.3: version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: json-buffer "3.0.1" levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -986,67 +841,65 @@ levn@^0.4.1: locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== math-intrinsics@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== mime-db@1.52.0: version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.35: +mime-types@^2.1.12: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" minimatch@^10.2.2: - version "10.2.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" - integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== + version "10.2.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== dependencies: - brace-expansion "^5.0.2" + brace-expansion "^5.0.8" minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" ms@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" optionator@^0.9.3: version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: deep-is "^0.1.3" @@ -1058,156 +911,156 @@ optionator@^0.9.3: p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -picomatch@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" - integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== +"picomatch@^3 || ^4", picomatch@^4.0.4: + version "4.0.7" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz" + integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^3.9.4: - version "3.9.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.4.tgz#a9c477cf1614376bd1f6bbc593d8c0d414bcec87" - integrity sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg== + version "3.9.6" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz" + integrity sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g== proxy-from-env@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz" integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== punycode@^2.1.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== reusify@^1.0.4: version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" semver@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + version "7.8.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== tinyglobby@^0.2.15: - version "0.2.15" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" - integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + version "0.2.17" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz" + integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== dependencies: fdir "^6.5.0" - picomatch "^4.0.3" + picomatch "^4.0.4" ts-api-utils@^2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" + resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz" integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== tslib@2.7.0: version "2.7.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== tsx@^4.22.4: version "4.22.4" - resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.22.4.tgz#0ab3b7fb4ec7feeee74e5b1f26337caa71e44700" + resolved "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz" integrity sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg== dependencies: esbuild "~0.28.0" @@ -1216,61 +1069,61 @@ tsx@^4.22.4: type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@6.0.2: +typescript@>=4.8.4, "typescript@>=4.8.4 <6.1.0", typescript@6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.2.tgz#0b1bfb15f68c64b97032f3d78abbf98bdbba501f" + resolved "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz" integrity sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ== undici-types@~6.19.2: version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== undici-types@~8.3.0: version "8.3.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.3.0.tgz#44e9fc9f3244648cdea35e4f9bb2d681e9410809" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz" integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" word-wrap@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@8.21.0: version "8.21.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.0.tgz#012e413fc07429945121b0c153158c4343086951" + resolved "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz" integrity sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 3c996a47b02120d93f1a60633984bc1b1f3f6481 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:03:05 -0600 Subject: [PATCH 02/31] Reduce tutorial friction --- README.md | 26 +-- bridge/.env.example | 5 +- bridge/README.md | 55 ++++++ bridge/bridge-offchain-worker/README.md | 160 ++--------------- bridge/contracts/CONTRIBUTING.md | 50 ++++++ bridge/contracts/DEPLOY.md | 162 +---------------- bridge/contracts/abi_generation.md | 11 +- bridge/custom-contracts-bridging/README.md | 154 +++++++--------- bridge/hello-bridge/README.md | 36 +++- loan/.env.example | 2 + loan/contracts/abi_generation.md | 2 +- loan/scripts/README.md | 24 +-- package.json | 1 + shared/check_setup.ts | 199 +++++++++++++++++++++ 14 files changed, 449 insertions(+), 438 deletions(-) create mode 100644 bridge/README.md create mode 100644 bridge/contracts/CONTRIBUTING.md create mode 100644 shared/check_setup.ts diff --git a/README.md b/README.md index fcb7a000..521f3da8 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # ๐ŸŒ‰ ASC Testnet Bridge Examples ๐ŸŒ‰ -This repository is designed as a starting point for users and builders alike to explore Creditcoin -cross-chain features on USC devnet. Learn how to use the Creditcoin Decentralized Oracle through -guided tutorials where you set up and interact with your own decentralized bridge and loan examples. +This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCBase` + `ASCMinter`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). + +Learn how to use the Creditcoin Decentralized Oracle through guided tutorials where you set up and interact with your own decentralized bridge and loan examples. ## Repository layout ``` usc-testnet-bridge-examples/ -โ”œโ”€โ”€ bridge/ # ASC bridge tutorials + bridge contracts -โ”‚ โ”œโ”€โ”€ contracts/ # ASCBase, ASCMinter, BridgeTestToken, โ€ฆ (local ASCBase; EvmV1Decoder from @gluwa/usc-contracts) +โ”œโ”€โ”€ bridge/ # Simplified ASC bridge tutorials + contracts (ASCBase, ASCMinter) +โ”‚ โ”œโ”€โ”€ contracts/ # Local readability contracts; EvmV1Decoder lib from @gluwa/usc-contracts โ”‚ โ”œโ”€โ”€ hello-bridge/ โ”‚ โ”œโ”€โ”€ custom-contracts-bridging/ โ”‚ โ””โ”€โ”€ bridge-offchain-worker/ @@ -19,6 +19,8 @@ usc-testnet-bridge-examples/ โ””โ”€โ”€ shared/utils/ # Proof builder + submit helpers shared by both examples ``` +Package overview: [bridge/README.md](./bridge/README.md) (simplified bridge model and what is out of scope). + ## Before you start Before attempting any of the tutorials, make sure the following are installed: @@ -48,20 +50,24 @@ carries only public network defaults: > [!CAUTION] > Keep your private key in `.env` only. Never put it in `.env.example`, which is tracked by git. -Then source your env file to load configuration for all examples: +Then source your env file and verify connectivity: ```bash source .env +yarn utils:check_setup hello # Hello Bridge +yarn utils:check_setup bridge # after custom-contracts deploy ``` +Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ADDRESS` in `.env` and reuse for the loan tutorial. + ## Tutorials We recommend going through them in this order: -1. ๐Ÿ“š [Hello Bridge] -2. ๐Ÿ“š [Custom Contracts Bridging] -3. ๐Ÿ“š [Bridge Offchain Worker] -4. ๐Ÿ“š [Loan Flow] +1. ๐Ÿ“š [Hello Bridge] โ€” pre-deployed contracts, manual proof submit +2. ๐Ÿ“š [Custom Contracts Bridging] โ€” deploy your ASC + optional worker (ยง7) +3. ๐Ÿ“š [Bridge Offchain Worker] โ€” short reference (main flow in custom-contracts ยง7) +4. ๐Ÿ“š [Loan Flow] โ€” reuses decoder library from bridge ## External Resources diff --git a/bridge/.env.example b/bridge/.env.example index 65aeb94f..f2be8147 100644 --- a/bridge/.env.example +++ b/bridge/.env.example @@ -22,9 +22,8 @@ SOURCE_CHAIN_RPC_URL="" CREDITCOIN_WALLET_PRIVATE_KEY="" ## User-deployed (custom-contracts-bridging + offchain worker) -# Your burner ERC20 on the source chain SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" -# Your ASCMinter on USC devnet ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" -# Your wrapped ASCMintableToken on USC devnet ASC_CUSTOM_MINTABLE_TOKEN="" +# Reuse across bridge + loan deploys on the same Creditcoin network +EVM_V1_DECODER_LIBRARY_ADDRESS="" diff --git a/bridge/README.md b/bridge/README.md new file mode 100644 index 00000000..06ccc072 --- /dev/null +++ b/bridge/README.md @@ -0,0 +1,55 @@ +# Bridge examples (simplified ASC bridge) + +These tutorials implement a **simplified readability bridge** โ€” not the production [write-ability](https://github.com/gluwa/usc-contracts/tree/main/contracts/write-ability) stack (Outbox, Relayer, Inbox, liquidity operators, etc.). + +## What this example uses + +| Piece | Role | +|-------|------| +| **`ASCBase`** (local) | Verify a source-chain transaction via the native verifier precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. | +| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens once per proof. | +| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs. The `write-ability/common/` path is **only** this decoder โ€” not the messaging bridge. | +| **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | + +Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint**. + +Anyone with a valid proof can submit `execute`; the offchain worker is optional UX automation. + +## What this example does not use + +- Outbox / Relayer / Inbox messaging +- `ASCBridgeLiquidityOperator`, `ClientBridgeLiquidityOperator`, or other write-ability bridge contracts +- Trusted relayers or operator-gated mint paths + +For the production bridge architecture, see [usc-contracts](https://github.com/gluwa/usc-contracts) and Creditcoin's write-ability documentation. + +## Layout + +``` +bridge/ +โ”œโ”€โ”€ contracts/sol/ ASCBase, ASCMinter, MintableToken, BridgeTestToken +โ”œโ”€โ”€ hello-bridge/ Pre-deployed ASC + burner (minimal setup) +โ”œโ”€โ”€ custom-contracts-bridging/ Deploy your own ASC + wrapped token +โ””โ”€โ”€ bridge-offchain-worker/ Automate proof submission after burns +``` + +## Tutorials (recommended order) + +1. [Hello Bridge](./hello-bridge/README.md) +2. [Custom Contracts Bridging](./custom-contracts-bridging/README.md) +3. [Bridge Offchain Worker](./bridge-offchain-worker/README.md) + +Deployment troubleshooting: [contracts/CONTRIBUTING.md](./contracts/CONTRIBUTING.md). + +## Before you run tutorials + +```sh +yarn utils:check_setup hello # after configuring .env for Hello Bridge +yarn utils:check_setup bridge # after custom-contracts deploy +``` + +Deploy `EvmV1Decoder` **once** per Creditcoin network; set `EVM_V1_DECODER_LIBRARY_ADDRESS` in `.env` and reuse for loan deploys. + +## Environment + +Network and RPC settings live in the repository root [`.env`](../.env). Bridge-specific variable names are listed in [`.env.example`](./.env.example) as a convenience split. diff --git a/bridge/bridge-offchain-worker/README.md b/bridge/bridge-offchain-worker/README.md index 9c6c88f5..9c687b9c 100644 --- a/bridge/bridge-offchain-worker/README.md +++ b/bridge/bridge-offchain-worker/README.md @@ -1,166 +1,30 @@ # Bridge Offchain Worker -> [!TIP] -> This tutorial builds on the previous [Custom Contract Bridging] example -make sure to check it out -> before moving on! +> [!NOTE] +> **Start here:** [Custom Contract Bridging ยง7 โ€” Automate proof submission](../custom-contracts-bridging/README.md#7-automate-proof-submission-optional) for the integrated tutorial step. -So far we have seen [how to initiate a trustless bridge transaction] and -[how to customize our trustless bridging logic]. In this tutorial, we will be seeing how to automate -our interaction with the Creditcoin oracle so that end users only have to submit _a single transaction_ -on the Sepolia _source chain_. +This folder contains the worker implementation (`worker.ts`). It watches Sepolia for burns and calls `ASCMinter.execute` on CC3 Testnet โ€” same simplified bridge as the manual `yarn custom_bridge:submit_query` path, with no write-ability Relayer. -## What is an Offchain Worker +## Quick start -An Offchain Worker is a script responsible for watching the state of a _source chain_: in this -case, Sepolia. In more complex cases this would also listen to state changes on the Creditcoin execution chain. The worker queries to our -_Attestcoin Smart Contract_ on Creditcoin in response to specific events on -each chain. With an offchain worker, all the end user needs to do is sign a single transaction -on the source chain kicking off cross-chain interaction. - -## 0. Install - -For our tutorial scripts to function properly, we need to install dependencies first. - -```bash -yarn install -``` - -## 1. Setup - -This is the same as in [Hello Bridge]. If you have not already done so, follow the installation -steps in the [setup] section there. - -Additionally you need to have the `ASC_CUSTOM_MINTER_CONTRACT_ADDRESS` environment variable set as seen in the [Custom Contract Bridging] tutorial. - -Once that is done, you will need to set up some additional configuration for the offchain worker. -Check the `.env` file in the root of the repository and make sure it contains the following: - -```env -# ============================================================================ # -# Source Chain Configuration # -# ============================================================================ # - -# RPC endpoint for Sepolia -SOURCE_CHAIN_RPC_URL="https://sepolia.infura.io/v3/" - -# Address of the ERC20 token contract on source chain -SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS= - -# ============================================================================ # -# Creditcoin ASC Chain Configuration # -# ============================================================================ # - -# Address of your custom minter contract on Creditcoin -ASC_CUSTOM_MINTER_CONTRACT_ADDRESS= - -# Private key of the wallet that will submit mint requests -CREDITCOIN_WALLET_PRIVATE_KEY= -``` - -Once everyting is fine reload your `.env` file with: +Prerequisites: complete [Custom Contract Bridging](../custom-contracts-bridging/README.md) (deployed `ASCMinter`, wrapped token, and `.env` vars). ```sh source .env -``` - -## 2. Start the Offchain Worker - -Once you have your worker configured, it's time to start automating some queries! - -Run the following command to start the worker: - -```sh yarn offchain:start_worker ``` -Once it's up and running, you start to see the following logs: - -```bash -Starting... -Worker started! Listening for burn events... -Polling source chain from block 11073265 -Polling ASC chain from block 4969330 -``` - -## 3. Burning the tokens you want to bridge - -Like we did in the previous tutorials, we start the bridging process by burning the funds we want to -bridge on Sepolia. This time however this will be the only transaction we need to submit! The rest -will be handled automatically by the worker ๐Ÿค– - -Open a new terminal window and source the `.env` file once again - -```sh -source .env -``` - -Then run the following command to initiate the burn: - -```sh -cast send --rpc-url $SOURCE_CHAIN_RPC_URL \ - $SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS \ - "burn(uint256)" 2000 \ - --private-key $CREDITCOIN_WALLET_PRIVATE_KEY -``` - -> [!TIP] -> If your worker is not running when the transaction is being processed on the source chain it will not pick up -> the event! This example is made for simplicity, a more robust worker would be able to read events from -> previous blocks and have more complex event filters. - -## 4. Monitor the Offchain Worker - -At this point, you should see the worker picking up the event. - -```bash -Detected burn of 2000 tokens from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 at 0xb5f8f1000432f92521021642a915999407c61ed1a9f13c2e6c37f6ac9b6eb6f0 -Transaction 0xb5f8f1000432f92521021642a915999407c61ed1a9f13c2e6c37f6ac9b6eb6f0 found in block 418 -Waiting for block 418 attestation on Creditcoin... -``` - -Eventually, you should see a message like this one: - -```bash -Tokens minted! Contract: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512, To: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, Amount: 2000, QueryId: 0x3bfcf46c80011ef4280b7212e4fa11e5be314b12c9ddd56a74e83c2964b3e9be -``` - -That's it! All it took was a single transaction on your end to initiate the bridging process, -providing for a _truly native UX_. - -## 5. Check Balance in CC3 Testnet ERC20 Contract - -As a final check, we can take a look at the balance of your account on Creditcoin to confirm that -the bridging process was successful. +Burn on Sepolia in another terminal; the worker handles attestation, proof fetch, and mint. -Run the following command to check your funds: - -```sh -WALLET_ADDRESS=$(cast wallet address --private-key $CREDITCOIN_WALLET_PRIVATE_KEY) -yarn utils:check_balance $ASC_CUSTOM_MINTABLE_TOKEN $WALLET_ADDRESS -``` - -It should show something like this: - -```bash -๐Ÿ”— Using RPC URL: https://rpc.cc3-testnet.creditcoin.network -๐Ÿ“ฆ Token: Bridge Test Token (BTKT) -๐Ÿงพ Raw Balance: 100000000000000004000 -๐Ÿ’ฐ Formatted Balance: 100.000000000000004 BTKT -``` +## Required env vars -## Conclusion +- `SOURCE_CHAIN_RPC_URL`, `SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS` +- `ASC_CUSTOM_MINTER_CONTRACT_ADDRESS`, `ASC_CUSTOM_MINTABLE_TOKEN` +- `CREDITCOIN_RPC_URL`, `CREDITCOIN_WALLET_PRIVATE_KEY`, `SOURCE_CHAIN_KEY`, `PROOF_BUILDER_URL` -Congratulations! You've managed to run your first offchain worker! +## Next -The next tutorial will take what we've learned here and increase the complexity with a more real world -example: a [Loan Flow], in which a user can lend tokens to another and through the oracle validate that the -repayments truly happened. +[Loan Flow](../../loan/scripts/README.md) โ€” cross-chain loan state with a similar worker pattern. [Custom Contract Bridging]: ../custom-contracts-bridging/README.md -[how to initiate a trustless bridge transaction]: ../hello-bridge/README.md -[how to customize our trustless bridging logic]: ../custom-contracts-bridging/README.md -[Hello Bridge]: ../hello-bridge/README.md -[setup]: ../hello-bridge/README.md#1-setup -[Custom Contracts Bridging]: ../custom-contracts-bridging/README.md#33-update-environment-with-your-asc-contract-address -[ASC Gitbook]: https://docs.attestcoin.org/ [Loan Flow]: ../../loan/scripts/README.md diff --git a/bridge/contracts/CONTRIBUTING.md b/bridge/contracts/CONTRIBUTING.md new file mode 100644 index 00000000..1f00bcdf --- /dev/null +++ b/bridge/contracts/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Bridge contracts โ€” contributor notes + +Tutorial users can skip this file. It covers ABI regeneration and deployment troubleshooting for maintainers. + +## Regenerate ABIs + +After changing contracts under `bridge/contracts/sol/`: + +```sh +yarn +./bridge/contracts/abi-creator.sh +``` + +For loan contracts: `./loan/contracts/abi-creator.sh`. + +Bridge contracts are local simplified readability contracts (`ASCBase`, `ASCMinter`). Only `EvmV1Decoder` is imported from `@gluwa/usc-contracts`. + +## Deploy troubleshooting + +Common `forge create` issues when deploying `ASCMinter` with a linked `EvmV1Decoder` library: + +### Nonce / "already known" + +- Wait 10โ€“30s and retry `ASCMinter` deploy (decoder address unchanged). +- Or raise gas: `--gas-price `. +- Or set explicit nonce: `cast nonce
--rpc-url $CREDITCOIN_RPC_URL` then `--nonce`. + +### Insufficient funds + +Fund the deployer on Creditcoin testnet (Discord faucet). Check balance: + +```bash +cast balance --rpc-url $CREDITCOIN_RPC_URL +``` + +### Hangs on confirmation + +`forge create` waits for inclusion; slow RPCs can take 1โ€“5 minutes. Check the explorer if unsure. + +### Library link errors + +Ensure `--libraries` uses the deployed decoder address: + +```bash +--libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: +``` + +The `write-ability/common/` path is the shared decoder library only โ€” not the Outbox/Relayer stack. + +After deploy, submit proofs with `yarn hello_bridge:submit_query` or `yarn custom_bridge:submit_query`. diff --git a/bridge/contracts/DEPLOY.md b/bridge/contracts/DEPLOY.md index 9c11372f..bf5dd1e2 100644 --- a/bridge/contracts/DEPLOY.md +++ b/bridge/contracts/DEPLOY.md @@ -1,161 +1,5 @@ -# Troubleshooting ASCMinter Contract Deployment +# Deployment troubleshooting -This guide explains how to deploy the `ASCMinter` contract to Creditcoin ASC networks. The contract serves as a bridge minter that verifies cross-chain proofs and mints tokens on Creditcoin. +Moved to [CONTRIBUTING.md](./CONTRIBUTING.md). -## Verify Deployment - -After deployment, verify the contract was deployed correctly by checking the contract on the block explorer at https://creditcoin-testnet.blockscout.com - -Search for your contract address to see deployment details and transaction history. - -## Contract Details - -- **Contract Name**: `ASCMinter` -- **Constructor**: No parameters required -- **Mint Amount**: the amount burned on the source chain, taken from the proven burn event -- **Wrapped token**: `ASCMinter` is not itself a token. It mints through a separate - `ASCMintableToken`, registered with `wrapOriginToken`. The one used by these tutorials is - `BridgeTestToken`, name "Bridge Test Token", symbol "BTKT", 18 decimals (inherited from - OpenZeppelin ERC20). - -## Network Information - -- **RPC URL**: `https://rpc.cc3-testnet.creditcoin.network` -- **Explorer**: https://creditcoin-testnet.blockscout.com -- **Purpose**: Development and testing - -## Troubleshooting - -### Error: "already known" or "nonce already used" - -This error typically means a transaction with that nonce was already submitted to the network. This can happen when: - -- A previous transaction with the same nonce is pending -- An underfunded transaction is stuck in the mempool - -You **can** redeploy - each deployment creates a **new contract address**. - -**Solution 1: Increase gas fees** (if transaction is underfunded) - -If the transaction is stuck in the mempool due to insufficient gas fees, retry with increased gas: - -```bash -# Retry deployment with higher gas price -forge create \ - --broadcast \ - --rpc-url $CREDITCOIN_RPC_URL \ - --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --gas-price \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ - bridge/contracts/sol/ASCMinter.sol:ASCMinter -``` - -**Solution 2: Wait and retry** (recommended) - -Usually waiting for around 10s-30s and trying to deploy again will work, if you already managed to deploy the decoder you only need to -retry redeploying the `ASCMinter` contract: - -```bash -forge create \ - --broadcast \ - --rpc-url $CREDITCOIN_RPC_URL \ - --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ - bridge/contracts/sol/ASCMinter.sol:ASCMinter -``` - -**Solution 3: Check current nonce and use next** - -```bash -# Get your current nonce -cast nonce --rpc-url $CREDITCOIN_RPC_URL - -# Deploy with the next nonce (replace X with current_nonce + 1) -forge create \ - --broadcast \ - --rpc-url $CREDITCOIN_RPC_URL \ - --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --nonce X \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ - bridge/contracts/sol/ASCMinter.sol:ASCMinter -``` - -**Solution 4: Check if contract was already deployed** - -- Check the block explorer for your address -- Look for contract creation transactions -- Each deployment creates a unique contract address - -### Error: "insufficient funds" - -- Make sure your wallet has enough native tokens (CTC) to pay for gas fees -- Request test tokens from the [Creditcoin Discord faucet] -- Check your balance: `cast balance --rpc-url ` - -### Error: "nonce too low" - -- Wait a moment and try again -- Or manually set a higher nonce using `--nonce` flag -- Check your current nonce: `cast nonce --rpc-url ` - -### Error: Command hangs or takes too long - -`forge create` waits for transaction confirmation, which can take time on slower networks. - -**Solution:** - -- Be patient - it may take 1-5 minutes depending on network conditions -- If it hangs indefinitely, press `Ctrl+C` and check the block explorer to see if the transaction went through -- You can check transaction status manually using the block explorer - -### Error: "execution reverted" - -- Check that the contract compiled successfully: `forge build` -- Verify you're using the correct RPC URL for your target network -- Ensure your wallet has sufficient funds -- Check the contract constructor doesn't require parameters (it doesn't) - -### Error: "could not decode result data" - -This usually means the contract doesn't exist at the address or the RPC is incorrect. - -- Verify the contract address is correct -- Check you're using the right RPC URL for the network -- Ensure the contract was actually deployed (check block explorer) - -## Advanced: Manual Nonce Management - -If you're experiencing persistent nonce issues, you can manually manage nonces: - -```bash -# Get current nonce -CURRENT_NONCE=$(cast nonce --rpc-url $CREDITCOIN_RPC_URL) - -# Deploy with explicit nonce -forge create \ - --broadcast \ - --rpc-url $CREDITCOIN_RPC_URL \ - --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --nonce $((CURRENT_NONCE + 1)) \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ - bridge/contracts/sol/ASCMinter.sol:ASCMinter -``` - -## Next Steps - -After deploying your contract: - -1. **Save the contract address** - you'll need it for all interactions -2. **Update your scripts** - replace any hardcoded contract addresses with your new address -3. **Verify the deployment** - check the block explorer to confirm the contract was deployed -4. **Submit queries** - use `yarn submit_query_2` with your contract address - -For more information on using the deployed contract, see the [README.md](../custom-contracts-bridging/README.md) for the Custom Contract Bridging example. - -## Additional Resources - -- [Foundry Documentation](https://book.getfoundry.sh/) -- [Creditcoin Documentation](https://docs.creditcoin.org/) -- [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts) - -[Creditcoin Discord faucet]: https://discord.com/channels/762302877518528522/1414985542235459707 +Tutorial deploy steps live in [custom-contracts-bridging/README.md](../custom-contracts-bridging/README.md). diff --git a/bridge/contracts/abi_generation.md b/bridge/contracts/abi_generation.md index 5b1d65a8..76ccd252 100644 --- a/bridge/contracts/abi_generation.md +++ b/bridge/contracts/abi_generation.md @@ -1,10 +1,3 @@ -# ABI Generation +# ABI generation -After changing bridge contracts under `bridge/contracts/sol/`, regenerate ABIs from the repository root: - -```sh -yarn -./bridge/contracts/abi-creator.sh -``` - -For loan contracts, use `./loan/contracts/abi-creator.sh` instead. +Moved to [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index 3fbd3934..53f4c317 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -1,5 +1,8 @@ # Custom Contract Bridging +> [!NOTE] +> Deploys the **simplified** local `ASCMinter` / `ASCBase` pair โ€” direct `execute` minting, no relayer contracts. See [bridge/README.md](../README.md). + > [!TIP] > This tutorial builds on the previous [Hello Bridge] example -make sure to check it out before > moving on! @@ -65,47 +68,13 @@ Once again, reload your `.env` file with: source .env ``` -## 3. Deploy Your Own Custom Bridging Contract - -In the next two steps we will be deploying our own bridging contract called `ASCMinter.sol` - -Attestcoin smart contracts (ASCs) such as `ASCMinter` are intended to be deployed by DApp -builders. Here, our ASC is used only for bridging tokens. A ASC exposes functions which -internally make use of the Creditcoin Oracle to verify cross-chain data. It then interprets -those data and uses them to trigger DApp business logic. - -For instance, our `ASCMinter` looks for fields like `from`, `to`, and `amount` in the -cross-chain data we submit to it. With those fields, the contract can verify whether or not a -token burn took place, how many tokens it needs to mint on Creditcoin, and which address it -should mint them to. - -### 3.1 Modify The Bridge Smart Contract - -As an exercise, we will be modifying our `ASCMinter` so that it mints _twice_ the amount -of tokens which were burned on our _source chain_. - -> [!NOTE] -> This is for demonstration purposes only, as bridging this way dilutes the value of our `TEST` -> token each time we bridge it. - -Start by opening the file `bridge/contracts/sol/ASCMinter.sol`. Next, navigate to the following line -inside of the `_processMint` function: - -```sol -ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue); -``` - -Update it so that your `ASCMinter` contract mints twice the `burntValue` -of tokens it should on Creditcoin. The resulting line should look something like: +## 3. Deploy your bridging stack -```sol -ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue * 2); -``` +You will deploy the stock `ASCMinter` from this repo โ€” **no contract edits required** for the default path. -### 3.2 Deploy Your Decoder Library and Modified Contract +### 3.1 Deploy `EvmV1Decoder` (once per Creditcoin network) -First we need to deploy our `EvmV1Decoder` library so that we can reference it in our -`ASCMinter`. We do so like this: +Deploy the shared decoding library. **Save this address** โ€” you can reuse it for the [Loan Flow](../../loan/scripts/README.md) without deploying again. ```bash forge create \ @@ -115,29 +84,34 @@ forge create \ node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder ``` -You should get some output with the address of the library you just deployed: +Add the library address to your root `.env` so later steps (and the loan tutorial) can reuse it: -```bash -Deployer: 0x20dB67795C2AEb4De075986b0D4217A109FEF2B5 -Deployed to: 0x128A6492F875Bd92C07D7F0050fc5c265dbc849B -Transaction hash: 0x04e524d4578851b06bd2196710b0c9890fff6bf29d40999c5fb02dab0c428fca +```env +EVM_V1_DECODER_LIBRARY_ADDRESS= +``` + +Reload: + +```sh +source .env ``` -Use the contract address shown in `Deployed to:` to deploy your `ASCMinter` using the following command: +> **Note:** The library lives under `write-ability/common/` in `@gluwa/usc-contracts` โ€” that is only a shared decoder, not the Outbox/Relayer bridge stack. + +### 3.2 Deploy `ASCMinter` ```bash forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` -> [!IMPORTANT] -> Don't forget to replace `` with the address of your deployed decoder contract! +If deployment fails (nonce, gas, etc.), see [Contributor deploy notes](../contracts/CONTRIBUTING.md). -You should get some output with the address of the contract you just deployed: +You should get output with the contract address: ```bash Deployer: 0x20dB67795C2AEb4De075986b0D4217A109FEF2B5 @@ -145,27 +119,23 @@ Deployed to: 0xCDf3e9eC93015a1B3047d087296C1cE096f33f74 Transaction hash: 0xe86e3c2f77fd050a4120dbd195668af2f3d94f3a41b5db21643c53c1ac3cc212 ``` -If you have issues with deployment during this step, see the [Deployment Troubleshooting Guide] - ### 3.3 Update environment with your ASC contract address -Save the address of the contract. And modify the following entry in the `.env` file found at the root of the -repository: +Save the address and update the root `.env`: ```env ASC_CUSTOM_MINTER_CONTRACT_ADDRESS= ``` -Once again, reload your `.env` file with: +Reload: ```sh source .env ``` -### 3.4 Deploy Minter ERC20 Contract and register with ASC Minter +### 3.4 Deploy wrapped token and register the route -Now that we've deployed our ASC which triggers the minting action, we need to connect the ERC20 contract in -which we will mint wrapped tokens! +Deploy the wrapped ERC20 owned by your minter: ```bash forge create \ @@ -176,29 +146,13 @@ forge create \ --constructor-args "$ASC_CUSTOM_MINTER_CONTRACT_ADDRESS" ``` -You should get some output with the address of the ERC20 token you just deployed: - -```bash -Deployer: 0x20dB67795C2AEb4De075986b0D4217A109FEF2B5 -Deployed to: 0xD1A5c57654636146417B589aED99C56b9c73C510 -Transaction hash: 0x07f87a00b117f9d16c5a20a461d00cbce87aa76994af727a02d73da3aca622f1 -``` - -Modify the following entry in the `.env` file found at the root of the -repository: +Update `.env`: ```env ASC_CUSTOM_MINTABLE_TOKEN= ``` -Once again, reload your `.env` file with: - -```sh -source .env -``` - -Our last step is to register the ERC20 token as the wrapped version of the source chain token we intend -to bridge. +Register the Sepolia source token โ†’ wrapped token mapping: ```bash cast send \ @@ -208,6 +162,12 @@ cast send \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY ``` +Optional preflight before burning: + +```sh +yarn utils:check_setup bridge +``` + ## 4. Burning the tokens you want to bridge The following few steps are similar to what we did in the [Hello Bridge] example. Start by burning @@ -275,37 +235,51 @@ WALLET_ADDRESS=$(cast wallet address --private-key $CREDITCOIN_WALLET_PRIVATE_KE yarn utils:check_balance $ASC_CUSTOM_MINTABLE_TOKEN $WALLET_ADDRESS ``` -This will return your balance in whole (BTKT) token units. - -Notice how you now have _twice_ the amount of tokens you originally burned on Sepolia! +This will return your balance in whole (BTKT) token units. With the default `ASCMinter`, the minted amount should **match** the amount you burned on Sepolia. It should show something like this: ```bash ๐Ÿ”— Using RPC URL: https://rpc.cc3-testnet.creditcoin.network ๐Ÿ“ฆ Token: Bridge Test Token (BTKT) -๐Ÿงพ Raw Balance: 100000000000000000000 -๐Ÿ’ฐ Formatted Balance: 100.0 BTKT +๐Ÿงพ Raw Balance: 50000000000000000000 +๐Ÿ’ฐ Formatted Balance: 50.0 BTKT Decimals for token micro unit: 18 ``` -## Conclusion +## 7. Automate proof submission (optional) -Congratulations! You've set up your first custom smart contracts which make use of the Creditcoin -Decentralized Oracle! +The same burn โ†’ proof โ†’ `execute` flow can run automatically so users only sign the **burn** on Sepolia. Start the worker (requires steps 2โ€“3 complete): + +```sh +yarn offchain:start_worker +``` + +In another terminal, burn tokens โ€” the worker submits the mint proof when attestation completes: + +```sh +cast send --rpc-url $SOURCE_CHAIN_RPC_URL \ + $SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS \ + "burn(uint256)" 2000 \ + --private-key $CREDITCOIN_WALLET_PRIVATE_KEY +``` + +The worker is **UX-only** โ€” anyone can still call `execute` manually with `yarn custom_bridge:submit_query`. Full worker walkthrough: [bridge-offchain-worker/README.md](../bridge-offchain-worker/README.md). + +## Optional challenge: mint 2ร— burned amount + +To experiment with customizing ASC logic, edit `bridge/contracts/sol/ASCMinter.sol` so `_processMint` mints `burntValue * 2`, redeploy the minter (reusing the same `EVM_V1_DECODER_LIBRARY_ADDRESS`), and repeat the burn + submit flow. This dilutes token supply and is for learning only. + +## Conclusion -The next tutorial: [Bridge Offchain Worker], will take another important step towards developing a mature, production ready -cross-chain DApp. That step is automation! We automate using an **offchain worker** which submits -queries automatically. This _vastly_ improves UX by making it so the -end user only has to sign a _single_ transaction to initiate the bridging procedure. +Congratulations! You've deployed your own simplified ASC bridge (`ASCMinter` + wrapped token) and completed a trustless mint. -In practice, DApp builders will want to conduct all cross-chain queries via an offchain worker in -order to ensure robustness and streamline UX. +Next: [Loan Flow](../../loan/scripts/README.md) reuses your decoder library and extends the pattern with cross-chain loan state. [Hello Bridge]: ../hello-bridge/README.md [setup]: ../hello-bridge/README.md#1-setup [step 2]: #2-deploy-a-test-erc20-contract-on-sepolia -[step 3.2]: #32-deploy-your-decoder-library-and-modified-contract +[step 3.2]: #32-deploy-ascminter [step 5]: #5-submit-a-mint-query-to-the-asc-contract -[Deployment Troubleshooting Guide]: ../contracts/DEPLOY.md +[Contributor deploy notes]: ../contracts/CONTRIBUTING.md [Bridge Offchain Worker]: ../bridge-offchain-worker/README.md diff --git a/bridge/hello-bridge/README.md b/bridge/hello-bridge/README.md index 1bfc68f6..1ac27aa6 100644 --- a/bridge/hello-bridge/README.md +++ b/bridge/hello-bridge/README.md @@ -1,13 +1,37 @@ # Hello Bridge +> [!NOTE] +> This tutorial uses the **simplified ASC bridge** (`ASCMinter` + local `ASCBase`), not the write-ability Outbox/Relayer stack. Overview: [bridge/README.md](../README.md). + This tutorial introduces you to one of the most common uses for a cross chain oracle, **cross chain -bridging!** Cross-chain bridging on Creditcoin can be broken down into three broad steps: +bridging!** The flow is: + +1. **Burn** tokens on the source chain (Sepolia). +2. **Submit proof + mint** on Creditcoin โ€” steps 2 and 3 are combined in `yarn hello_bridge:submit_query` (proof generation, attestation wait, and `ASCMinter.execute`). + +## Pre-deployed contracts (no deploy step) + +Hello Bridge uses **shared tutorial contracts** already deployed on CC3 Testnet and Sepolia. The repository root [`.env`](../../.env) includes pre-filled values for: + +| Variable | Purpose | +|----------|---------| +| `SOURCE_CHAIN_KEY` | Sepolia chain key on CC3 Testnet (`1`) | +| `PROOF_BUILDER_URL` | Proof builder for CC3 Testnet | +| `CREDITCOIN_RPC_URL` | CC3 Testnet RPC | +| `SOURCE_CHAIN_CONTRACT_ADDRESS` | Pre-deployed Sepolia burner ERC20 | +| `ASC_MINTER_CONTRACT_ADDRESS` | Pre-deployed `ASCMinter` on CC3 Testnet | +| `ASC_MINTABLE_TOKEN` | Pre-deployed wrapped token | + +Copy those lines into your own `.env` if starting fresh. Variable names are also listed in [bridge/.env.example](../.env.example). + +You only need to add **`CREDITCOIN_WALLET_PRIVATE_KEY`** and **`SOURCE_CHAIN_RPC_URL`** (Infura Sepolia), then run: + +```sh +source .env +yarn utils:check_setup hello +``` -1. To begin, the `ERC20` tokens to bridge are burned using a smart contract on our _source chain_ - (in this case, Sepolia). -2. Then, we generate merkle and continuity proofs corresponding to our source chain token burn -3. Using the proofs we generated, we call our minter Attestcoin smart contract (ASC) which will internally call the Creditcoin oracle's native proof verifier -4. After that the same contract will mint the tokens on Creditcoin +If all checks pass, continue with [Setup](#1-setup) below. ## 0. Install diff --git a/loan/.env.example b/loan/.env.example index 2b7d9a9b..a85425f4 100644 --- a/loan/.env.example +++ b/loan/.env.example @@ -14,6 +14,8 @@ ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" # ERC20 used to fund/repay loans on the source chain SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" +# Reuse from bridge tutorial if already deployed +EVM_V1_DECODER_LIBRARY_ADDRESS="" ## Role-specific wallets # Lender account (registers loans, funds on source chain) diff --git a/loan/contracts/abi_generation.md b/loan/contracts/abi_generation.md index 00e3d7ec..5981c564 100644 --- a/loan/contracts/abi_generation.md +++ b/loan/contracts/abi_generation.md @@ -7,4 +7,4 @@ yarn ./loan/contracts/abi-creator.sh ``` -Loan contracts import `ASCBase` and decoding libraries from `@gluwa/usc-contracts` (see `loan/foundry.toml` remappings). +Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts`. ABI regeneration: see [bridge/contracts/CONTRIBUTING.md](../../bridge/contracts/CONTRIBUTING.md). diff --git a/loan/scripts/README.md b/loan/scripts/README.md index cad4a3a6..9702032b 100644 --- a/loan/scripts/README.md +++ b/loan/scripts/README.md @@ -1,8 +1,7 @@ # Loan Flow > [!TIP] -> This tutorial builds on the previous [Bridge Offchain Worker] example -make sure to check it out -> before moving on! +> Complete [Custom Contract Bridging](../../bridge/custom-contracts-bridging/README.md) (and optionally the offchain worker in ยง7) before this tutorial. Now that we know how to build an offchain worker to coordinate between two separate chains, we need a more advanced example that includes not only communication but also state tracking. @@ -67,8 +66,12 @@ Grab the contract address and add it to the `.env` file at the root of the repos SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS= ``` -Now we need to deploy a `EvmV1Decoder` library so that we can reference it in our -`ASCLoanManager`. We do so like this: +Now we need to deploy `ASCLoanManager`. It links the same **`EvmV1Decoder`** library as the bridge tutorial. + +> [!TIP] +> If you completed [Custom Contract Bridging](../../bridge/custom-contracts-bridging/README.md), reuse `EVM_V1_DECODER_LIBRARY_ADDRESS` from your `.env` โ€” **skip redeploying the decoder**. + +If you have not deployed a decoder yet: ```bash forge create \ @@ -78,12 +81,10 @@ forge create \ node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder ``` -You should get some output with the address of the library you just deployed: +Save the address in `.env`: -```bash -Deployer: 0x20dB67795C2AEb4De075986b0D4217A109FEF2B5 -Deployed to: 0xfE119359B96Bb1A32c14d32dD6b7E2f977Cd1060 -Transaction hash: 0xd1689ff2d26fd9e271d8c03e7933ce98f2f38836dd18441f6240e86a5816ec8d +```env +EVM_V1_DECODER_LIBRARY_ADDRESS= ``` Use the contract address shown in `Deployed to:` to deploy your `ASCLoanManager` using the following command: @@ -93,12 +94,11 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: \ + --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager ``` -> [!IMPORTANT] -> Don't forget to replace `` with the address of your deployed decoder contract! +You should get deployment output with your `ASCLoanManager` address. As before, grab the address and add it to the `.env` file, like so: diff --git a/package.json b/package.json index 7f6968f0..ebf4178d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "loan_flow:inspect_loan": "tsx loan/scripts/inspect.ts", "loan_flow:start_worker": "tsx loan/scripts/worker.ts", "utils:check_balance": "node shared/utils/check_balance.js", + "utils:check_setup": "tsx shared/check_setup.ts", "check-format": "prettier --check .", "eslint": "eslint -c .eslintrc.js --ignore-pattern .eslintrc.js --ignore-pattern check_balance.js --max-warnings 0 --ext .ts --ext .js .", "format": "prettier --write .", diff --git a/shared/check_setup.ts b/shared/check_setup.ts new file mode 100644 index 00000000..b8505d46 --- /dev/null +++ b/shared/check_setup.ts @@ -0,0 +1,199 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; + +import { isValidContractAddress, isValidPrivateKey } from './utils'; + +dotenv.config({ override: true }); + +const VERIFIER_PRECOMPILE = '0x0000000000000000000000000000000000000FD2'; + +type CheckResult = { ok: boolean; message: string }; + +function pass(message: string): CheckResult { + return { ok: true, message }; +} + +function fail(message: string): CheckResult { + return { ok: false, message }; +} + +async function checkRpc(label: string, url: string | undefined): Promise { + if (!url?.trim()) { + return fail(`${label}: not set`); + } + try { + const provider = new ethers.JsonRpcProvider(url); + const network = await provider.getNetwork(); + const block = await provider.getBlockNumber(); + return pass(`${label}: reachable (chainId ${network.chainId}, block ${block})`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + return fail(`${label}: unreachable (${msg})`); + } +} + +async function checkProofBuilder(url: string | undefined): Promise { + if (!url?.trim()) { + return fail('PROOF_BUILDER_URL: not set'); + } + try { + const response = await fetch(url.replace(/\/$/, ''), { method: 'GET' }); + if (response.ok || response.status === 404 || response.status === 405) { + return pass(`PROOF_BUILDER_URL: reachable (${response.status})`); + } + return fail(`PROOF_BUILDER_URL: HTTP ${response.status}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + return fail(`PROOF_BUILDER_URL: unreachable (${msg})`); + } +} + +async function checkVerifierPrecompile(creditcoinRpc: string | undefined): Promise { + if (!creditcoinRpc?.trim()) { + return fail('Verifier precompile: skipped (no CREDITCOIN_RPC_URL)'); + } + try { + const provider = new ethers.JsonRpcProvider(creditcoinRpc); + const code = await provider.getCode(VERIFIER_PRECOMPILE); + if (code && code !== '0x') { + return pass('Native verifier precompile (0xFD2): present'); + } + return fail('Native verifier precompile (0xFD2): no code at address (wrong network?)'); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + return fail(`Verifier precompile check failed (${msg})`); + } +} + +async function checkContractCode( + label: string, + rpcUrl: string | undefined, + address: string | undefined +): Promise { + if (!isValidContractAddress(address)) { + return fail(`${label}: not configured`); + } + if (!rpcUrl?.trim()) { + return fail(`${label}: skipped (no RPC URL)`); + } + try { + const provider = new ethers.JsonRpcProvider(rpcUrl); + const code = await provider.getCode(address!); + if (code && code !== '0x') { + return pass(`${label}: contract code found at ${address}`); + } + return fail(`${label}: no code at ${address}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + return fail(`${label}: check failed (${msg})`); + } +} + +function printResults(results: CheckResult[]): boolean { + let allOk = true; + for (const result of results) { + console.log(`${result.ok ? 'โœ“' : 'โœ—'} ${result.message}`); + if (!result.ok) { + allOk = false; + } + } + return allOk; +} + +async function runHelloBridgeChecks(): Promise { + const results: CheckResult[] = []; + + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + results.push( + sourceChainKey > 0 + ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) + : fail('SOURCE_CHAIN_KEY: missing or invalid') + ); + + results.push( + isValidPrivateKey(process.env.CREDITCOIN_WALLET_PRIVATE_KEY) + ? pass('CREDITCOIN_WALLET_PRIVATE_KEY: set') + : fail('CREDITCOIN_WALLET_PRIVATE_KEY: missing or invalid') + ); + + results.push(await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL)); + results.push(await checkRpc('SOURCE_CHAIN_RPC_URL', process.env.SOURCE_CHAIN_RPC_URL)); + results.push(await checkProofBuilder(process.env.PROOF_BUILDER_URL)); + results.push(await checkVerifierPrecompile(process.env.CREDITCOIN_RPC_URL)); + results.push( + await checkContractCode( + 'ASC_MINTER_CONTRACT_ADDRESS', + process.env.CREDITCOIN_RPC_URL, + process.env.ASC_MINTER_CONTRACT_ADDRESS + ) + ); + results.push( + await checkContractCode( + 'SOURCE_CHAIN_CONTRACT_ADDRESS (Sepolia burner)', + process.env.SOURCE_CHAIN_RPC_URL, + process.env.SOURCE_CHAIN_CONTRACT_ADDRESS + ) + ); + + return printResults(results); +} + +async function runBridgeChecks(): Promise { + const results: CheckResult[] = []; + + results.push(await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL)); + results.push(await checkRpc('SOURCE_CHAIN_RPC_URL', process.env.SOURCE_CHAIN_RPC_URL)); + results.push(await checkProofBuilder(process.env.PROOF_BUILDER_URL)); + results.push(await checkVerifierPrecompile(process.env.CREDITCOIN_RPC_URL)); + + results.push( + isValidPrivateKey(process.env.CREDITCOIN_WALLET_PRIVATE_KEY) + ? pass('CREDITCOIN_WALLET_PRIVATE_KEY: set') + : fail('CREDITCOIN_WALLET_PRIVATE_KEY: missing or invalid') + ); + + if (isValidContractAddress(process.env.EVM_V1_DECODER_LIBRARY_ADDRESS)) { + results.push( + pass(`EVM_V1_DECODER_LIBRARY_ADDRESS: ${process.env.EVM_V1_DECODER_LIBRARY_ADDRESS} (reuse for loan deploy)`) + ); + } else { + results.push( + pass('EVM_V1_DECODER_LIBRARY_ADDRESS: not set (deploy EvmV1Decoder once in custom-contracts tutorial)') + ); + } + + return printResults(results); +} + +async function main(): Promise { + const mode = (process.argv[2] ?? 'hello').toLowerCase(); + + console.log(`Tutorial setup check (${mode})\n`); + + let ok: boolean; + switch (mode) { + case 'hello': + case 'hello-bridge': + ok = await runHelloBridgeChecks(); + break; + case 'bridge': + case 'custom': + ok = await runBridgeChecks(); + break; + default: + console.error(`Unknown mode "${mode}". Use: hello | bridge`); + process.exit(1); + } + + if (!ok) { + console.error('\nFix the items above, then re-run: yarn utils:check_setup'); + process.exit(1); + } + + console.log('\nSetup looks good. You can continue the tutorial.'); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); From 3e41b2ba2aee9e46e108ceff01a24e5e27a307cf Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:03:18 -0600 Subject: [PATCH 03/31] Add verification tooling, contract tests, and CI hardening. --- .github/workflows/bridge-offchain-worker.yml | 10 +-- .../workflows/custom-contracts-bridging.yml | 10 +-- .github/workflows/hello-bridge.yml | 10 +-- .github/workflows/loan-flow.yml | 10 +-- .github/workflows/solidity.yml | 15 +++- .gitignore | 1 + README.md | 2 + VERIFICATION.md | 78 +++++++++++++++++++ bridge/.env.example | 27 +++---- bridge/README.md | 2 +- bridge/contracts/CONTRIBUTING.md | 4 +- bridge/contracts/sol/ASCMinter.sol | 2 +- bridge/custom-contracts-bridging/README.md | 6 +- bridge/foundry.toml | 12 ++- bridge/test/ASCMinterBurnLog.t.sol | 51 ++++++++++++ bridge/test/harness/ASCMinterHarness.sol | 15 ++++ loan/.env.example | 18 +---- loan/contracts/sol/ASCLoanManager.sol | 2 +- loan/foundry.toml | 13 +++- loan/scripts/README.md | 4 +- loan/test/ASCLoanManagerSourceBinding.t.sol | 73 +++++++++++++++++ loan/test/harness/ASCLoanManagerHarness.sol | 20 +++++ package.json | 3 + shared/check_setup.ts | 51 +++++++++++- 24 files changed, 368 insertions(+), 71 deletions(-) create mode 100644 VERIFICATION.md create mode 100644 bridge/test/ASCMinterBurnLog.t.sol create mode 100644 bridge/test/harness/ASCMinterHarness.sol create mode 100644 loan/test/ASCLoanManagerSourceBinding.t.sol create mode 100644 loan/test/harness/ASCLoanManagerHarness.sol diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index a751f6e8..c13e2b62 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ -loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ -node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -217,7 +217,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ ---libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -228,7 +228,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ -bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ + bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_MINTER_CONTRACT_ADDRESS" 2>&1) echo "$OUTPUT" ASC_MINTABLE_TOKEN=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -308,4 +308,4 @@ bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ killall -9 attestor killall -9 creditcoin3-node killall -9 node - killall -9 tsx + killall -9 tsx \ No newline at end of file diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index 1ecaad9c..0625936e 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ -loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ -node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -221,7 +221,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ ---libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -232,7 +232,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ -bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ + bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_MINTER_CONTRACT_ADDRESS" 2>&1) echo "$OUTPUT" ASC_MINTABLE_TOKEN=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -305,4 +305,4 @@ bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ run: | killall -9 attestor_zombienet killall -9 attestor - killall -9 creditcoin3-node + killall -9 creditcoin3-node \ No newline at end of file diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index 8ec7bb03..15884b58 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key ${{ steps.env.outputs.source_chain_private_key }} \ -loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ -node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -217,7 +217,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ ---libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -228,7 +228,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ -bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ + bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ --constructor-args "$ASC_MINTER_CONTRACT_ADDRESS" 2>&1) echo "$OUTPUT" ASC_MINTABLE_TOKEN=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -304,4 +304,4 @@ bridge/contracts/sol/BridgeTestToken.sol:BridgeTestToken \ run: | killall -9 attestor_zombienet killall -9 attestor - killall -9 creditcoin3-node + killall -9 creditcoin3-node \ No newline at end of file diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 7b58e13a..fdae4523 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ -loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ -loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract 2>&1) + loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract 2>&1) echo "$OUTPUT" AUX_LOAN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "aux_loan_contract_address=$AUX_LOAN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -218,7 +218,7 @@ loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract 2>&1) --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ -node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -227,7 +227,7 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ ---libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager 2>&1) echo "$OUTPUT" ASC_LOAN_MANAGER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -427,4 +427,4 @@ node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decod killall -9 attestor killall -9 creditcoin3-node killall -9 node - killall -9 tsx + killall -9 tsx \ No newline at end of file diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index be59caee..32fec92c 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -17,13 +17,22 @@ jobs: steps: - uses: actions/checkout@v7 + - name: Checkout usc-contracts (sibling for forge remappings) + uses: actions/checkout@v7 + with: + repository: gluwa/usc-contracts + path: ../usc-contracts + token: ${{ secrets.GITHUB_TOKEN }} + - name: Install foundry run: | export FOUNDRY_DIR="$HOME/.foundry" - # first install it curl -L https://foundry.paradigm.xyz | bash ~/.foundry/bin/foundryup + - name: Install forge-std + run: git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std + - name: Install Node.js uses: actions/setup-node@v6 with: @@ -31,12 +40,14 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Compile smart contracts + - name: Compile and test smart contracts run: | yarn install ~/.foundry/bin/forge build --root bridge ~/.foundry/bin/forge build --root loan + ~/.foundry/bin/forge test --root bridge + ~/.foundry/bin/forge test --root loan check-abi-on-disk: runs-on: ubuntu-26.04 diff --git a/.gitignore b/.gitignore index 389a8e50..9ac1caf5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ **/dist/ **/target/ **/node_modules/ +lib/forge-std/ **/*.rs.bk *.swp **/._* diff --git a/README.md b/README.md index 521f3da8..1e07c684 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ yarn utils:check_setup bridge # after custom-contracts deploy Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ADDRESS` in `.env` and reuse for the loan tutorial. +Full verification checklist: [VERIFICATION.md](./VERIFICATION.md). + ## Tutorials We recommend going through them in this order: diff --git a/VERIFICATION.md b/VERIFICATION.md new file mode 100644 index 00000000..099e9d43 --- /dev/null +++ b/VERIFICATION.md @@ -0,0 +1,78 @@ +# Verification checklist + +Use this to validate the repository locally before release or deployment. + +## Automated (CI + local) + +From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH` and a sibling [`usc-contracts`](https://github.com/gluwa/usc-contracts) checkout at `../usc-contracts` on the **default branch**, which provides `contracts/common/EvmV1Decoder.sol` and `contracts/readability/ASCBase.sol`): + +```sh +git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std # once +# sibling: ../usc-contracts should track usc-contracts main (or CI default branch) +yarn verify +``` + +This runs: + +| Step | Command | What it checks | +|------|---------|----------------| +| TypeScript | `yarn typecheck` | Tutorial scripts compile | +| Lint | `yarn eslint` | TS/JS style | +| Contracts | `yarn build` | `forge build` for bridge + loan | +| Unit tests | `forge test` | Loan source-contract binding + bridge burn-log parsing | + +### CI (`solidity.yml`) + +- Compiles bridge and loan contracts +- Regenerates ABIs and fails on drift +- Runs `forge test` for bridge and loan (requires `usc-contracts` checkout + `forge-std`) + +## Network preflight (optional, needs `.env`) + +Does **not** submit transactions. Verifies RPC, proof builder, native verifier precompile (`0xFD2`), and chain-key attestation: + +```sh +source .env +yarn utils:check_setup network # RPC + attestation only +yarn utils:check_setup hello # + hello-bridge contract addresses +yarn utils:check_setup bridge # + wallet key for custom bridge +``` + +## Manual end-to-end โ€” Hello Bridge + +- [ ] `yarn utils:check_setup hello` passes +- [ ] Mint test tokens on Sepolia (`cast send โ€ฆ mint`) +- [ ] Burn tokens (`cast send โ€ฆ burn`) โ€” save tx hash +- [ ] `yarn hello_bridge:submit_query ` โ€” expect `Proof generation successful!` then `Tokens minted!` +- [ ] `yarn utils:check_balance $ASC_MINTABLE_TOKEN $WALLET` โ€” balance matches burn amount + +Expected mint output (abbreviated): + +```text +Block attested! Generating proof... +Proof generation successful! +Proof submitted: 0x... +Tokens minted! Contract: 0x..., To: 0x..., Amount: 50000000000000000000, QueryId: 0x... +``` + +## Manual end-to-end โ€” Custom bridge + loan + +- [ ] Deploy `EvmV1Decoder` once; set `EVM_V1_DECODER_LIBRARY_ADDRESS` +- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` +- [ ] Burn โ†’ `yarn custom_bridge:submit_query` โ†’ mint succeeds once +- [ ] Second submit with same proof reverts (`Query already processed`) +- [ ] Deploy loan stack; `yarn loan_flow:register_source_contract` +- [ ] Fund/repay on Sepolia; worker or manual proof updates loan status +- [ ] Spoofed `LoanFunded` from unregistered contract cannot mark loan funded (covered by unit tests) + +## Loan source-contract binding (unit tests) + +`forge test --root loan` must pass: + +- `testFundLog_rejectsUnregisteredSource` +- `testFundLog_rejectsWrongEmitter` +- `testRepayLog_rejectsWrongEmitter` +- `testFundLog_acceptsRegisteredEmitter` +- `testRepayLog_acceptsRegisteredEmitter` + +These assert fund/repay proofs must come from the registered `sourceLoanContract` address. diff --git a/bridge/.env.example b/bridge/.env.example index f2be8147..8c68ecb7 100644 --- a/bridge/.env.example +++ b/bridge/.env.example @@ -1,24 +1,17 @@ # Bridge example environment -# Copy to bridge/.env or merge into the repository root .env +# Convenience split from root `.env` โ€” use the same CC3 Testnet values. -## Pre-deployed / network defaults (hello-bridge uses these as-is on USC devnet) -# Source chain key for Sepolia on USC devnet (chain ID 11155111 on Ethereum) -SOURCE_CHAIN_KEY=8 -# Proof builder for USC devnet -PROOF_BUILDER_URL="https://prover.usc-devnet.creditcoin.network/" -# Pre-deployed burner ERC20 on Sepolia (hello-bridge tutorial) -SOURCE_CHAIN_CONTRACT_ADDRESS="" -# Pre-deployed ASC minter on USC devnet (hello-bridge tutorial) -ASC_MINTER_CONTRACT_ADDRESS="" -# Pre-deployed wrapped token on USC devnet (hello-bridge tutorial) -ASC_MINTABLE_TOKEN="" -# USC devnet RPC (USC = network layer name only) -CREDITCOIN_RPC_URL="https://rpc.usc-devnet.creditcoin.network" +## Pre-deployed / network defaults (hello-bridge) +# Source chain key for Sepolia on CC3 Testnet (different from its chain ID, which is 11155111) +SOURCE_CHAIN_KEY=1 +PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" +SOURCE_CHAIN_CONTRACT_ADDRESS="0x0F24FD9e0524BA53d3f0A4A40350Adf5370b4A53" +ASC_MINTER_CONTRACT_ADDRESS="0x2Be9B8640ED32815d3B9e8C92AbcD3F15F07396f" +ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" +CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" -## User-provided (required for all bridge tutorials) -# Sepolia (or other source chain) RPC URL +## User-provided SOURCE_CHAIN_RPC_URL="" -# Wallet used on USC devnet and source chain CREDITCOIN_WALLET_PRIVATE_KEY="" ## User-deployed (custom-contracts-bridging + offchain worker) diff --git a/bridge/README.md b/bridge/README.md index 06ccc072..37c82b24 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -8,7 +8,7 @@ These tutorials implement a **simplified readability bridge** โ€” not the produc |-------|------| | **`ASCBase`** (local) | Verify a source-chain transaction via the native verifier precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. | | **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens once per proof. | -| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs. The `write-ability/common/` path is **only** this decoder โ€” not the messaging bridge. | +| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/decoding/`). Not the messaging bridge. | | **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint**. diff --git a/bridge/contracts/CONTRIBUTING.md b/bridge/contracts/CONTRIBUTING.md index 1f00bcdf..236fb519 100644 --- a/bridge/contracts/CONTRIBUTING.md +++ b/bridge/contracts/CONTRIBUTING.md @@ -42,9 +42,9 @@ cast balance --rpc-url $CREDITCOIN_RPC_URL Ensure `--libraries` uses the deployed decoder address: ```bash ---libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder: +--libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: ``` -The `write-ability/common/` path is the shared decoder library only โ€” not the Outbox/Relayer stack. +The library is published in npm at `contracts/decoding/EvmV1Decoder.sol` (deploy via `forge create` in tutorials). Solidity imports use `@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol` with a sibling `usc-contracts` checkout. After deploy, submit proofs with `yarn hello_bridge:submit_query` or `yarn custom_bridge:submit_query`. diff --git a/bridge/contracts/sol/ASCMinter.sol b/bridge/contracts/sol/ASCMinter.sol index afa9784c..eb89ca1d 100644 --- a/bridge/contracts/sol/ASCMinter.sol +++ b/bridge/contracts/sol/ASCMinter.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import {ASCMintableToken, ASC_MINTER} from "./MintableToken.sol"; import {ASCBase} from "./ASCBase.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; contract ASCMinter is ASCBase { enum MinterActions { diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index 53f4c317..8b3bf615 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -81,7 +81,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder ``` Add the library address to your root `.env` so later steps (and the loan tutorial) can reuse it: @@ -96,7 +96,7 @@ Reload: source .env ``` -> **Note:** The library lives under `write-ability/common/` in `@gluwa/usc-contracts` โ€” that is only a shared decoder, not the Outbox/Relayer bridge stack. +> **Note:** Deploy from `contracts/decoding/EvmV1Decoder.sol` in the npm package. That library is a shared receipt decoder โ€” not the Outbox/Relayer bridge stack. ### 3.2 Deploy `ASCMinter` @@ -105,7 +105,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ + --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` diff --git a/bridge/foundry.toml b/bridge/foundry.toml index b98d3363..1eb765f1 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -1,13 +1,19 @@ [profile.default] out = "out" src = "contracts/sol" -libs = ["../../node_modules"] +test = "test" +libs = ["../node_modules", "../lib"] +# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts +# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. +allow_paths = ["../../usc-contracts"] remappings = [ - "@gluwa/usc-contracts/=../../usc-contracts/contracts/", + "@openzeppelin/=../node_modules/@openzeppelin/", + "@gluwa/usc-contracts/=../../usc-contracts/", + "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" optimizer = true optimizer_runs = 200 -via_ir = false +via_ir = true evm_version = "shanghai" verbosity = 2 diff --git a/bridge/test/ASCMinterBurnLog.t.sol b/bridge/test/ASCMinterBurnLog.t.sol new file mode 100644 index 00000000..5c85201f --- /dev/null +++ b/bridge/test/ASCMinterBurnLog.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test} from "forge-std/Test.sol"; +import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +contract ASCMinterBurnLogTest is Test { + ASCMinterHarness internal minter; + + bytes32 internal constant BURN_EVENT_SIGNATURE = + 0x17dc4d6f69d484e59be774c29b47d2fa4c14af2e01df42fc5643ac968f4d427e; + + function setUp() public { + minter = new ASCMinterHarness(); + } + + function testBurnLog_decodesRecipientAndAmount() public view { + address token = address(0x1111); + address burner = address(0x2222); + uint256 amount = 50 ether; + + (address origin, address from, uint256 value) = + minter.exposeProcessBurnLogs(_burnLog(token, burner, amount)); + + assertEq(origin, token); + assertEq(from, burner); + assertEq(value, amount); + } + + function testBurnLog_rejectsWrongEventSignature() public { + EvmV1Decoder.LogEntry[] memory logs = _burnLog(address(0x1), address(0x2), 1); + logs[0].topics[0] = bytes32(uint256(0xdead)); + + vm.expectRevert("Not TokensBurnedForBridging event"); + minter.exposeProcessBurnLogs(logs); + } + + function _burnLog(address token, address from, uint256 amount) + internal + pure + returns (EvmV1Decoder.LogEntry[] memory logs) + { + logs = new EvmV1Decoder.LogEntry[](1); + logs[0].address_ = token; + logs[0].topics = new bytes32[](2); + logs[0].topics[0] = BURN_EVENT_SIGNATURE; + logs[0].topics[1] = bytes32(uint256(uint160(from))); + logs[0].data = abi.encode(amount); + } +} diff --git a/bridge/test/harness/ASCMinterHarness.sol b/bridge/test/harness/ASCMinterHarness.sol new file mode 100644 index 00000000..9470be2e --- /dev/null +++ b/bridge/test/harness/ASCMinterHarness.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +contract ASCMinterHarness is ASCMinter { + function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) + external + pure + returns (address originTokenAddress, address from, uint256 value) + { + return _processBurnLogs(burnLogs); + } +} diff --git a/loan/.env.example b/loan/.env.example index a85425f4..dcf5b016 100644 --- a/loan/.env.example +++ b/loan/.env.example @@ -1,27 +1,17 @@ # Loan example environment -# Copy to loan/.env or merge into the repository root .env +# Convenience split from root `.env` โ€” use the same CC3 Testnet values. -## Shared network settings -SOURCE_CHAIN_KEY=8 -PROOF_BUILDER_URL="https://prover.usc-devnet.creditcoin.network/" -CREDITCOIN_RPC_URL="https://rpc.usc-devnet.creditcoin.network" +SOURCE_CHAIN_KEY=1 +PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" +CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" SOURCE_CHAIN_RPC_URL="" -## User-deployed loan contracts -# ASCLoanManager on USC devnet ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" -# AuxiliaryLoanContract on the source chain SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" -# ERC20 used to fund/repay loans on the source chain SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" # Reuse from bridge tutorial if already deployed EVM_V1_DECODER_LIBRARY_ADDRESS="" -## Role-specific wallets -# Lender account (registers loans, funds on source chain) LENDER_WALLET_PRIVATE_KEY="" -# Borrower account (repays on source chain) BORROWER_WALLET_PRIVATE_KEY="" - -## Optional: also used when scripts need a single execution-chain key CREDITCOIN_WALLET_PRIVATE_KEY="" diff --git a/loan/contracts/sol/ASCLoanManager.sol b/loan/contracts/sol/ASCLoanManager.sol index dc09655f..a2ffb564 100644 --- a/loan/contracts/sol/ASCLoanManager.sol +++ b/loan/contracts/sol/ASCLoanManager.sol @@ -9,7 +9,7 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {LoanFlow, LoanStatus, LoanOrder, LoanTerms} from "./LoanTypes.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; /** diff --git a/loan/foundry.toml b/loan/foundry.toml index 9e5db51f..1eb765f1 100644 --- a/loan/foundry.toml +++ b/loan/foundry.toml @@ -1,14 +1,19 @@ [profile.default] out = "out" src = "contracts/sol" -libs = ["../../node_modules"] +test = "test" +libs = ["../node_modules", "../lib"] +# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts +# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. +allow_paths = ["../../usc-contracts"] remappings = [ - "@openzeppelin/=../../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../../usc-contracts/contracts/", + "@openzeppelin/=../node_modules/@openzeppelin/", + "@gluwa/usc-contracts/=../../usc-contracts/", + "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" optimizer = true optimizer_runs = 200 -via_ir = false +via_ir = true evm_version = "shanghai" verbosity = 2 diff --git a/loan/scripts/README.md b/loan/scripts/README.md index 9702032b..a6cc30f6 100644 --- a/loan/scripts/README.md +++ b/loan/scripts/README.md @@ -78,7 +78,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder ``` Save the address in `.env`: @@ -94,7 +94,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/write-ability/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ + --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager ``` diff --git a/loan/test/ASCLoanManagerSourceBinding.t.sol b/loan/test/ASCLoanManagerSourceBinding.t.sol new file mode 100644 index 00000000..2c8b3b19 --- /dev/null +++ b/loan/test/ASCLoanManagerSourceBinding.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ASCLoanManagerHarness} from "./harness/ASCLoanManagerHarness.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +/// @notice Regression tests for loan source-contract binding โ€” fund/repay logs must come from the registered source contract. +contract ASCLoanManagerSourceBindingTest is Test { + ASCLoanManagerHarness internal manager; + + bytes32 internal constant FUND_EVENT_SIGNATURE = + 0x9e71d2fb732e68272b7e74ecfd14638673c1d77e19a5d390a3ffff054d57c44b; + bytes32 internal constant REPAY_EVENT_SIGNATURE = + 0x040cee90ee4799897c30ca04e5feb6fa43dbba9b6d084b4b257cdafd84ba013e; + + address internal registeredSource = address(0xA11CE); + address internal spoofedSource = address(0xBAD); + + function setUp() public { + manager = new ASCLoanManagerHarness(); + manager.registerSourceLoanContract(registeredSource); + } + + function testFundLog_acceptsRegisteredEmitter() public view { + uint256 loanId = manager.exposeProcessFundLogs(_fundLog(registeredSource, 42)); + assertEq(loanId, 42); + } + + function testFundLog_rejectsUnregisteredSource() public { + ASCLoanManagerHarness fresh = new ASCLoanManagerHarness(); + vm.expectRevert("Source loan contract not registered!"); + fresh.exposeProcessFundLogs(_fundLog(registeredSource, 1)); + } + + function testFundLog_rejectsWrongEmitter() public { + vm.expectRevert("LoanFunded event not emitted by registered source loan contract!"); + manager.exposeProcessFundLogs(_fundLog(spoofedSource, 1)); + } + + function testRepayLog_acceptsRegisteredEmitter() public view { + (uint256 loanId, uint256 amount) = manager.exposeProcessRepayLogs(_repayLog(registeredSource, 7, 1000)); + assertEq(loanId, 7); + assertEq(amount, 1000); + } + + function testRepayLog_rejectsWrongEmitter() public { + vm.expectRevert("LoanRepaid event not emitted by registered source loan contract!"); + manager.exposeProcessRepayLogs(_repayLog(spoofedSource, 7, 1000)); + } + + function _fundLog(address emitter, uint256 loanId) internal pure returns (EvmV1Decoder.LogEntry[] memory logs) { + logs = new EvmV1Decoder.LogEntry[](1); + logs[0].address_ = emitter; + logs[0].topics = new bytes32[](2); + logs[0].topics[0] = FUND_EVENT_SIGNATURE; + logs[0].topics[1] = bytes32(loanId); + logs[0].data = ""; + } + + function _repayLog(address emitter, uint256 loanId, uint256 amount) + internal + pure + returns (EvmV1Decoder.LogEntry[] memory logs) + { + logs = new EvmV1Decoder.LogEntry[](1); + logs[0].address_ = emitter; + logs[0].topics = new bytes32[](2); + logs[0].topics[0] = REPAY_EVENT_SIGNATURE; + logs[0].topics[1] = bytes32(loanId); + logs[0].data = abi.encode(amount); + } +} diff --git a/loan/test/harness/ASCLoanManagerHarness.sol b/loan/test/harness/ASCLoanManagerHarness.sol new file mode 100644 index 00000000..34d69169 --- /dev/null +++ b/loan/test/harness/ASCLoanManagerHarness.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ASCLoanManager} from "../../contracts/sol/ASCLoanManager.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +/// @dev Test harness exposing internal log validators for source-contract binding tests. +contract ASCLoanManagerHarness is ASCLoanManager { + function exposeProcessFundLogs(EvmV1Decoder.LogEntry[] memory fundLogs) external view returns (uint256) { + return _processFundLogs(fundLogs); + } + + function exposeProcessRepayLogs(EvmV1Decoder.LogEntry[] memory repayLogs) + external + view + returns (uint256 loanId, uint256 amount) + { + return _processRepayLogs(repayLogs); + } +} diff --git a/package.json b/package.json index ebf4178d..237cd8b0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "eslint": "eslint -c .eslintrc.js --ignore-pattern .eslintrc.js --ignore-pattern check_balance.js --max-warnings 0 --ext .ts --ext .js .", "format": "prettier --write .", "build": "forge build --root bridge && forge build --root loan", + "test": "forge test --root bridge && forge test --root loan", + "test:contracts": "forge test --root bridge && forge test --root loan", + "verify": "yarn typecheck && yarn eslint && yarn build && yarn test:contracts", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/shared/check_setup.ts b/shared/check_setup.ts index b8505d46..a5a70127 100644 --- a/shared/check_setup.ts +++ b/shared/check_setup.ts @@ -1,6 +1,7 @@ import dotenv from 'dotenv'; import { ethers } from 'ethers'; +import { chainInfo } from '@gluwa/usc-sdk'; import { isValidContractAddress, isValidPrivateKey } from './utils'; dotenv.config({ override: true }); @@ -100,6 +101,41 @@ function printResults(results: CheckResult[]): boolean { return allOk; } +async function checkChainKeyAttestation( + creditcoinRpc: string | undefined, + chainKey: number +): Promise { + if (!creditcoinRpc?.trim()) { + return fail('Chain attestation: skipped (no CREDITCOIN_RPC_URL)'); + } + if (!chainKey) { + return fail('Chain attestation: SOURCE_CHAIN_KEY missing'); + } + try { + const provider = new ethers.JsonRpcProvider(creditcoinRpc); + const info = new chainInfo.PrecompileChainInfoProvider(provider); + const latest = await info.getLatestAttestedHeightAndHash(chainKey); + return pass(`Chain key ${chainKey} attestation: latest height ${latest.height}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + return fail(`Chain attestation failed (${msg})`); + } +} + +async function runNetworkChecks(): Promise { + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + const results: CheckResult[] = [ + sourceChainKey > 0 + ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) + : fail('SOURCE_CHAIN_KEY: missing or invalid'), + await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL), + await checkProofBuilder(process.env.PROOF_BUILDER_URL), + await checkVerifierPrecompile(process.env.CREDITCOIN_RPC_URL), + await checkChainKeyAttestation(process.env.CREDITCOIN_RPC_URL, sourceChainKey), + ]; + return printResults(results); +} + async function runHelloBridgeChecks(): Promise { const results: CheckResult[] = []; @@ -135,16 +171,26 @@ async function runHelloBridgeChecks(): Promise { ) ); + results.push(await checkChainKeyAttestation(process.env.CREDITCOIN_RPC_URL, sourceChainKey)); + return printResults(results); } async function runBridgeChecks(): Promise { const results: CheckResult[] = []; + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + results.push( + sourceChainKey > 0 + ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) + : fail('SOURCE_CHAIN_KEY: missing or invalid') + ); + results.push(await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL)); results.push(await checkRpc('SOURCE_CHAIN_RPC_URL', process.env.SOURCE_CHAIN_RPC_URL)); results.push(await checkProofBuilder(process.env.PROOF_BUILDER_URL)); results.push(await checkVerifierPrecompile(process.env.CREDITCOIN_RPC_URL)); + results.push(await checkChainKeyAttestation(process.env.CREDITCOIN_RPC_URL, sourceChainKey)); results.push( isValidPrivateKey(process.env.CREDITCOIN_WALLET_PRIVATE_KEY) @@ -180,8 +226,11 @@ async function main(): Promise { case 'custom': ok = await runBridgeChecks(); break; + case 'network': + ok = await runNetworkChecks(); + break; default: - console.error(`Unknown mode "${mode}". Use: hello | bridge`); + console.error(`Unknown mode "${mode}". Use: hello | bridge | network`); process.exit(1); } From e81fbc548faf6745761ee8e7099c050f76e755e9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:59:25 +0000 Subject: [PATCH 04/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/workflows/bridge-offchain-worker.yml | 2 +- .../workflows/custom-contracts-bridging.yml | 2 +- .github/workflows/hello-bridge.yml | 2 +- .github/workflows/loan-flow.yml | 2 +- VERIFICATION.md | 156 +++++++-------- bridge/.env.example | 44 ++--- bridge/foundry.toml | 38 ++-- bridge/test/ASCMinterBurnLog.t.sol | 102 +++++----- bridge/test/harness/ASCMinterHarness.sol | 30 +-- loan/.env.example | 34 ++-- loan/contracts/abi-creator.sh | 34 ++-- loan/contracts/abi_generation.md | 20 +- loan/foundry.toml | 38 ++-- loan/test/ASCLoanManagerSourceBinding.t.sol | 146 +++++++------- loan/test/harness/ASCLoanManagerHarness.sol | 40 ++-- shared/submit_bridge_query.ts | 182 +++++++++--------- 16 files changed, 436 insertions(+), 436 deletions(-) diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index c13e2b62..ba4ee7fc 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -308,4 +308,4 @@ jobs: killall -9 attestor killall -9 creditcoin3-node killall -9 node - killall -9 tsx \ No newline at end of file + killall -9 tsx diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index 0625936e..08957ddb 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -305,4 +305,4 @@ jobs: run: | killall -9 attestor_zombienet killall -9 attestor - killall -9 creditcoin3-node \ No newline at end of file + killall -9 creditcoin3-node diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index 15884b58..a994a2f5 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -304,4 +304,4 @@ jobs: run: | killall -9 attestor_zombienet killall -9 attestor - killall -9 creditcoin3-node \ No newline at end of file + killall -9 creditcoin3-node diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index fdae4523..fe1ff0dd 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -427,4 +427,4 @@ jobs: killall -9 attestor killall -9 creditcoin3-node killall -9 node - killall -9 tsx \ No newline at end of file + killall -9 tsx diff --git a/VERIFICATION.md b/VERIFICATION.md index 099e9d43..eca6bf00 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -1,78 +1,78 @@ -# Verification checklist - -Use this to validate the repository locally before release or deployment. - -## Automated (CI + local) - -From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH` and a sibling [`usc-contracts`](https://github.com/gluwa/usc-contracts) checkout at `../usc-contracts` on the **default branch**, which provides `contracts/common/EvmV1Decoder.sol` and `contracts/readability/ASCBase.sol`): - -```sh -git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std # once -# sibling: ../usc-contracts should track usc-contracts main (or CI default branch) -yarn verify -``` - -This runs: - -| Step | Command | What it checks | -|------|---------|----------------| -| TypeScript | `yarn typecheck` | Tutorial scripts compile | -| Lint | `yarn eslint` | TS/JS style | -| Contracts | `yarn build` | `forge build` for bridge + loan | -| Unit tests | `forge test` | Loan source-contract binding + bridge burn-log parsing | - -### CI (`solidity.yml`) - -- Compiles bridge and loan contracts -- Regenerates ABIs and fails on drift -- Runs `forge test` for bridge and loan (requires `usc-contracts` checkout + `forge-std`) - -## Network preflight (optional, needs `.env`) - -Does **not** submit transactions. Verifies RPC, proof builder, native verifier precompile (`0xFD2`), and chain-key attestation: - -```sh -source .env -yarn utils:check_setup network # RPC + attestation only -yarn utils:check_setup hello # + hello-bridge contract addresses -yarn utils:check_setup bridge # + wallet key for custom bridge -``` - -## Manual end-to-end โ€” Hello Bridge - -- [ ] `yarn utils:check_setup hello` passes -- [ ] Mint test tokens on Sepolia (`cast send โ€ฆ mint`) -- [ ] Burn tokens (`cast send โ€ฆ burn`) โ€” save tx hash -- [ ] `yarn hello_bridge:submit_query ` โ€” expect `Proof generation successful!` then `Tokens minted!` -- [ ] `yarn utils:check_balance $ASC_MINTABLE_TOKEN $WALLET` โ€” balance matches burn amount - -Expected mint output (abbreviated): - -```text -Block attested! Generating proof... -Proof generation successful! -Proof submitted: 0x... -Tokens minted! Contract: 0x..., To: 0x..., Amount: 50000000000000000000, QueryId: 0x... -``` - -## Manual end-to-end โ€” Custom bridge + loan - -- [ ] Deploy `EvmV1Decoder` once; set `EVM_V1_DECODER_LIBRARY_ADDRESS` -- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` -- [ ] Burn โ†’ `yarn custom_bridge:submit_query` โ†’ mint succeeds once -- [ ] Second submit with same proof reverts (`Query already processed`) -- [ ] Deploy loan stack; `yarn loan_flow:register_source_contract` -- [ ] Fund/repay on Sepolia; worker or manual proof updates loan status -- [ ] Spoofed `LoanFunded` from unregistered contract cannot mark loan funded (covered by unit tests) - -## Loan source-contract binding (unit tests) - -`forge test --root loan` must pass: - -- `testFundLog_rejectsUnregisteredSource` -- `testFundLog_rejectsWrongEmitter` -- `testRepayLog_rejectsWrongEmitter` -- `testFundLog_acceptsRegisteredEmitter` -- `testRepayLog_acceptsRegisteredEmitter` - -These assert fund/repay proofs must come from the registered `sourceLoanContract` address. +# Verification checklist + +Use this to validate the repository locally before release or deployment. + +## Automated (CI + local) + +From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH` and a sibling [`usc-contracts`](https://github.com/gluwa/usc-contracts) checkout at `../usc-contracts` on the **default branch**, which provides `contracts/common/EvmV1Decoder.sol` and `contracts/readability/ASCBase.sol`): + +```sh +git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std # once +# sibling: ../usc-contracts should track usc-contracts main (or CI default branch) +yarn verify +``` + +This runs: + +| Step | Command | What it checks | +|------|---------|----------------| +| TypeScript | `yarn typecheck` | Tutorial scripts compile | +| Lint | `yarn eslint` | TS/JS style | +| Contracts | `yarn build` | `forge build` for bridge + loan | +| Unit tests | `forge test` | Loan source-contract binding + bridge burn-log parsing | + +### CI (`solidity.yml`) + +- Compiles bridge and loan contracts +- Regenerates ABIs and fails on drift +- Runs `forge test` for bridge and loan (requires `usc-contracts` checkout + `forge-std`) + +## Network preflight (optional, needs `.env`) + +Does **not** submit transactions. Verifies RPC, proof builder, native verifier precompile (`0xFD2`), and chain-key attestation: + +```sh +source .env +yarn utils:check_setup network # RPC + attestation only +yarn utils:check_setup hello # + hello-bridge contract addresses +yarn utils:check_setup bridge # + wallet key for custom bridge +``` + +## Manual end-to-end โ€” Hello Bridge + +- [ ] `yarn utils:check_setup hello` passes +- [ ] Mint test tokens on Sepolia (`cast send โ€ฆ mint`) +- [ ] Burn tokens (`cast send โ€ฆ burn`) โ€” save tx hash +- [ ] `yarn hello_bridge:submit_query ` โ€” expect `Proof generation successful!` then `Tokens minted!` +- [ ] `yarn utils:check_balance $ASC_MINTABLE_TOKEN $WALLET` โ€” balance matches burn amount + +Expected mint output (abbreviated): + +```text +Block attested! Generating proof... +Proof generation successful! +Proof submitted: 0x... +Tokens minted! Contract: 0x..., To: 0x..., Amount: 50000000000000000000, QueryId: 0x... +``` + +## Manual end-to-end โ€” Custom bridge + loan + +- [ ] Deploy `EvmV1Decoder` once; set `EVM_V1_DECODER_LIBRARY_ADDRESS` +- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` +- [ ] Burn โ†’ `yarn custom_bridge:submit_query` โ†’ mint succeeds once +- [ ] Second submit with same proof reverts (`Query already processed`) +- [ ] Deploy loan stack; `yarn loan_flow:register_source_contract` +- [ ] Fund/repay on Sepolia; worker or manual proof updates loan status +- [ ] Spoofed `LoanFunded` from unregistered contract cannot mark loan funded (covered by unit tests) + +## Loan source-contract binding (unit tests) + +`forge test --root loan` must pass: + +- `testFundLog_rejectsUnregisteredSource` +- `testFundLog_rejectsWrongEmitter` +- `testRepayLog_rejectsWrongEmitter` +- `testFundLog_acceptsRegisteredEmitter` +- `testRepayLog_acceptsRegisteredEmitter` + +These assert fund/repay proofs must come from the registered `sourceLoanContract` address. diff --git a/bridge/.env.example b/bridge/.env.example index 8c68ecb7..79cf789a 100644 --- a/bridge/.env.example +++ b/bridge/.env.example @@ -1,22 +1,22 @@ -# Bridge example environment -# Convenience split from root `.env` โ€” use the same CC3 Testnet values. - -## Pre-deployed / network defaults (hello-bridge) -# Source chain key for Sepolia on CC3 Testnet (different from its chain ID, which is 11155111) -SOURCE_CHAIN_KEY=1 -PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" -SOURCE_CHAIN_CONTRACT_ADDRESS="0x0F24FD9e0524BA53d3f0A4A40350Adf5370b4A53" -ASC_MINTER_CONTRACT_ADDRESS="0x2Be9B8640ED32815d3B9e8C92AbcD3F15F07396f" -ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" -CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" - -## User-provided -SOURCE_CHAIN_RPC_URL="" -CREDITCOIN_WALLET_PRIVATE_KEY="" - -## User-deployed (custom-contracts-bridging + offchain worker) -SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" -ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" -ASC_CUSTOM_MINTABLE_TOKEN="" -# Reuse across bridge + loan deploys on the same Creditcoin network -EVM_V1_DECODER_LIBRARY_ADDRESS="" +# Bridge example environment +# Convenience split from root `.env` โ€” use the same CC3 Testnet values. + +## Pre-deployed / network defaults (hello-bridge) +# Source chain key for Sepolia on CC3 Testnet (different from its chain ID, which is 11155111) +SOURCE_CHAIN_KEY=1 +PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" +SOURCE_CHAIN_CONTRACT_ADDRESS="0x0F24FD9e0524BA53d3f0A4A40350Adf5370b4A53" +ASC_MINTER_CONTRACT_ADDRESS="0x2Be9B8640ED32815d3B9e8C92AbcD3F15F07396f" +ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" +CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" + +## User-provided +SOURCE_CHAIN_RPC_URL="" +CREDITCOIN_WALLET_PRIVATE_KEY="" + +## User-deployed (custom-contracts-bridging + offchain worker) +SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" +ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" +ASC_CUSTOM_MINTABLE_TOKEN="" +# Reuse across bridge + loan deploys on the same Creditcoin network +EVM_V1_DECODER_LIBRARY_ADDRESS="" diff --git a/bridge/foundry.toml b/bridge/foundry.toml index 1eb765f1..a8bd05de 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -1,19 +1,19 @@ -[profile.default] -out = "out" -src = "contracts/sol" -test = "test" -libs = ["../node_modules", "../lib"] -# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts -# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. -allow_paths = ["../../usc-contracts"] -remappings = [ - "@openzeppelin/=../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../../usc-contracts/", - "forge-std/=../lib/forge-std/src/", -] -solc_version = "0.8.30" -optimizer = true -optimizer_runs = 200 -via_ir = true -evm_version = "shanghai" -verbosity = 2 +[profile.default] +out = "out" +src = "contracts/sol" +test = "test" +libs = ["../node_modules", "../lib"] +# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts +# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. +allow_paths = ["../../usc-contracts"] +remappings = [ + "@openzeppelin/=../node_modules/@openzeppelin/", + "@gluwa/usc-contracts/=../../usc-contracts/", + "forge-std/=../lib/forge-std/src/", +] +solc_version = "0.8.30" +optimizer = true +optimizer_runs = 200 +via_ir = true +evm_version = "shanghai" +verbosity = 2 diff --git a/bridge/test/ASCMinterBurnLog.t.sol b/bridge/test/ASCMinterBurnLog.t.sol index 5c85201f..f2668d2f 100644 --- a/bridge/test/ASCMinterBurnLog.t.sol +++ b/bridge/test/ASCMinterBurnLog.t.sol @@ -1,51 +1,51 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test} from "forge-std/Test.sol"; -import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; - -contract ASCMinterBurnLogTest is Test { - ASCMinterHarness internal minter; - - bytes32 internal constant BURN_EVENT_SIGNATURE = - 0x17dc4d6f69d484e59be774c29b47d2fa4c14af2e01df42fc5643ac968f4d427e; - - function setUp() public { - minter = new ASCMinterHarness(); - } - - function testBurnLog_decodesRecipientAndAmount() public view { - address token = address(0x1111); - address burner = address(0x2222); - uint256 amount = 50 ether; - - (address origin, address from, uint256 value) = - minter.exposeProcessBurnLogs(_burnLog(token, burner, amount)); - - assertEq(origin, token); - assertEq(from, burner); - assertEq(value, amount); - } - - function testBurnLog_rejectsWrongEventSignature() public { - EvmV1Decoder.LogEntry[] memory logs = _burnLog(address(0x1), address(0x2), 1); - logs[0].topics[0] = bytes32(uint256(0xdead)); - - vm.expectRevert("Not TokensBurnedForBridging event"); - minter.exposeProcessBurnLogs(logs); - } - - function _burnLog(address token, address from, uint256 amount) - internal - pure - returns (EvmV1Decoder.LogEntry[] memory logs) - { - logs = new EvmV1Decoder.LogEntry[](1); - logs[0].address_ = token; - logs[0].topics = new bytes32[](2); - logs[0].topics[0] = BURN_EVENT_SIGNATURE; - logs[0].topics[1] = bytes32(uint256(uint160(from))); - logs[0].data = abi.encode(amount); - } -} +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test} from "forge-std/Test.sol"; +import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +contract ASCMinterBurnLogTest is Test { + ASCMinterHarness internal minter; + + bytes32 internal constant BURN_EVENT_SIGNATURE = + 0x17dc4d6f69d484e59be774c29b47d2fa4c14af2e01df42fc5643ac968f4d427e; + + function setUp() public { + minter = new ASCMinterHarness(); + } + + function testBurnLog_decodesRecipientAndAmount() public view { + address token = address(0x1111); + address burner = address(0x2222); + uint256 amount = 50 ether; + + (address origin, address from, uint256 value) = + minter.exposeProcessBurnLogs(_burnLog(token, burner, amount)); + + assertEq(origin, token); + assertEq(from, burner); + assertEq(value, amount); + } + + function testBurnLog_rejectsWrongEventSignature() public { + EvmV1Decoder.LogEntry[] memory logs = _burnLog(address(0x1), address(0x2), 1); + logs[0].topics[0] = bytes32(uint256(0xdead)); + + vm.expectRevert("Not TokensBurnedForBridging event"); + minter.exposeProcessBurnLogs(logs); + } + + function _burnLog(address token, address from, uint256 amount) + internal + pure + returns (EvmV1Decoder.LogEntry[] memory logs) + { + logs = new EvmV1Decoder.LogEntry[](1); + logs[0].address_ = token; + logs[0].topics = new bytes32[](2); + logs[0].topics[0] = BURN_EVENT_SIGNATURE; + logs[0].topics[1] = bytes32(uint256(uint160(from))); + logs[0].data = abi.encode(amount); + } +} diff --git a/bridge/test/harness/ASCMinterHarness.sol b/bridge/test/harness/ASCMinterHarness.sol index 9470be2e..b57c34f5 100644 --- a/bridge/test/harness/ASCMinterHarness.sol +++ b/bridge/test/harness/ASCMinterHarness.sol @@ -1,15 +1,15 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; - -contract ASCMinterHarness is ASCMinter { - function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) - external - pure - returns (address originTokenAddress, address from, uint256 value) - { - return _processBurnLogs(burnLogs); - } -} +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +contract ASCMinterHarness is ASCMinter { + function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) + external + pure + returns (address originTokenAddress, address from, uint256 value) + { + return _processBurnLogs(burnLogs); + } +} diff --git a/loan/.env.example b/loan/.env.example index dcf5b016..c767359d 100644 --- a/loan/.env.example +++ b/loan/.env.example @@ -1,17 +1,17 @@ -# Loan example environment -# Convenience split from root `.env` โ€” use the same CC3 Testnet values. - -SOURCE_CHAIN_KEY=1 -PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" -CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" -SOURCE_CHAIN_RPC_URL="" - -ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" -SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" -SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" -# Reuse from bridge tutorial if already deployed -EVM_V1_DECODER_LIBRARY_ADDRESS="" - -LENDER_WALLET_PRIVATE_KEY="" -BORROWER_WALLET_PRIVATE_KEY="" -CREDITCOIN_WALLET_PRIVATE_KEY="" +# Loan example environment +# Convenience split from root `.env` โ€” use the same CC3 Testnet values. + +SOURCE_CHAIN_KEY=1 +PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" +CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" +SOURCE_CHAIN_RPC_URL="" + +ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" +SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" +SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" +# Reuse from bridge tutorial if already deployed +EVM_V1_DECODER_LIBRARY_ADDRESS="" + +LENDER_WALLET_PRIVATE_KEY="" +BORROWER_WALLET_PRIVATE_KEY="" +CREDITCOIN_WALLET_PRIVATE_KEY="" diff --git a/loan/contracts/abi-creator.sh b/loan/contracts/abi-creator.sh index 384c1eea..8b167dec 100644 --- a/loan/contracts/abi-creator.sh +++ b/loan/contracts/abi-creator.sh @@ -1,17 +1,17 @@ -#!/bin/bash - -# WARNING: execute this from the repository root. - -sol_directory="loan/contracts/sol" -abi_directory="loan/contracts/abi" - -for p in "$sol_directory"/*; do - file=$(basename "$p") - contract_name="${file//.sol/}" - file_with_extension="$contract_name.json" - solc --base-path . --include-path "node_modules" \ - --allow-paths "bridge/contracts/sol" \ - "$sol_directory/$file" \ - --combined-json abi --overwrite --json-indent 2 | \ - jq ".contracts[\"$p:$contract_name\"].abi // {}" > "$abi_directory/$file_with_extension" -done +#!/bin/bash + +# WARNING: execute this from the repository root. + +sol_directory="loan/contracts/sol" +abi_directory="loan/contracts/abi" + +for p in "$sol_directory"/*; do + file=$(basename "$p") + contract_name="${file//.sol/}" + file_with_extension="$contract_name.json" + solc --base-path . --include-path "node_modules" \ + --allow-paths "bridge/contracts/sol" \ + "$sol_directory/$file" \ + --combined-json abi --overwrite --json-indent 2 | \ + jq ".contracts[\"$p:$contract_name\"].abi // {}" > "$abi_directory/$file_with_extension" +done diff --git a/loan/contracts/abi_generation.md b/loan/contracts/abi_generation.md index 5981c564..02b6563c 100644 --- a/loan/contracts/abi_generation.md +++ b/loan/contracts/abi_generation.md @@ -1,10 +1,10 @@ -# ABI Generation - -After changing loan contracts under `loan/contracts/sol/`, regenerate ABIs from the repository root: - -```sh -yarn -./loan/contracts/abi-creator.sh -``` - -Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts`. ABI regeneration: see [bridge/contracts/CONTRIBUTING.md](../../bridge/contracts/CONTRIBUTING.md). +# ABI Generation + +After changing loan contracts under `loan/contracts/sol/`, regenerate ABIs from the repository root: + +```sh +yarn +./loan/contracts/abi-creator.sh +``` + +Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts`. ABI regeneration: see [bridge/contracts/CONTRIBUTING.md](../../bridge/contracts/CONTRIBUTING.md). diff --git a/loan/foundry.toml b/loan/foundry.toml index 1eb765f1..a8bd05de 100644 --- a/loan/foundry.toml +++ b/loan/foundry.toml @@ -1,19 +1,19 @@ -[profile.default] -out = "out" -src = "contracts/sol" -test = "test" -libs = ["../node_modules", "../lib"] -# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts -# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. -allow_paths = ["../../usc-contracts"] -remappings = [ - "@openzeppelin/=../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../../usc-contracts/", - "forge-std/=../lib/forge-std/src/", -] -solc_version = "0.8.30" -optimizer = true -optimizer_runs = 200 -via_ir = true -evm_version = "shanghai" -verbosity = 2 +[profile.default] +out = "out" +src = "contracts/sol" +test = "test" +libs = ["../node_modules", "../lib"] +# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts +# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. +allow_paths = ["../../usc-contracts"] +remappings = [ + "@openzeppelin/=../node_modules/@openzeppelin/", + "@gluwa/usc-contracts/=../../usc-contracts/", + "forge-std/=../lib/forge-std/src/", +] +solc_version = "0.8.30" +optimizer = true +optimizer_runs = 200 +via_ir = true +evm_version = "shanghai" +verbosity = 2 diff --git a/loan/test/ASCLoanManagerSourceBinding.t.sol b/loan/test/ASCLoanManagerSourceBinding.t.sol index 2c8b3b19..a1173388 100644 --- a/loan/test/ASCLoanManagerSourceBinding.t.sol +++ b/loan/test/ASCLoanManagerSourceBinding.t.sol @@ -1,73 +1,73 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {ASCLoanManagerHarness} from "./harness/ASCLoanManagerHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; - -/// @notice Regression tests for loan source-contract binding โ€” fund/repay logs must come from the registered source contract. -contract ASCLoanManagerSourceBindingTest is Test { - ASCLoanManagerHarness internal manager; - - bytes32 internal constant FUND_EVENT_SIGNATURE = - 0x9e71d2fb732e68272b7e74ecfd14638673c1d77e19a5d390a3ffff054d57c44b; - bytes32 internal constant REPAY_EVENT_SIGNATURE = - 0x040cee90ee4799897c30ca04e5feb6fa43dbba9b6d084b4b257cdafd84ba013e; - - address internal registeredSource = address(0xA11CE); - address internal spoofedSource = address(0xBAD); - - function setUp() public { - manager = new ASCLoanManagerHarness(); - manager.registerSourceLoanContract(registeredSource); - } - - function testFundLog_acceptsRegisteredEmitter() public view { - uint256 loanId = manager.exposeProcessFundLogs(_fundLog(registeredSource, 42)); - assertEq(loanId, 42); - } - - function testFundLog_rejectsUnregisteredSource() public { - ASCLoanManagerHarness fresh = new ASCLoanManagerHarness(); - vm.expectRevert("Source loan contract not registered!"); - fresh.exposeProcessFundLogs(_fundLog(registeredSource, 1)); - } - - function testFundLog_rejectsWrongEmitter() public { - vm.expectRevert("LoanFunded event not emitted by registered source loan contract!"); - manager.exposeProcessFundLogs(_fundLog(spoofedSource, 1)); - } - - function testRepayLog_acceptsRegisteredEmitter() public view { - (uint256 loanId, uint256 amount) = manager.exposeProcessRepayLogs(_repayLog(registeredSource, 7, 1000)); - assertEq(loanId, 7); - assertEq(amount, 1000); - } - - function testRepayLog_rejectsWrongEmitter() public { - vm.expectRevert("LoanRepaid event not emitted by registered source loan contract!"); - manager.exposeProcessRepayLogs(_repayLog(spoofedSource, 7, 1000)); - } - - function _fundLog(address emitter, uint256 loanId) internal pure returns (EvmV1Decoder.LogEntry[] memory logs) { - logs = new EvmV1Decoder.LogEntry[](1); - logs[0].address_ = emitter; - logs[0].topics = new bytes32[](2); - logs[0].topics[0] = FUND_EVENT_SIGNATURE; - logs[0].topics[1] = bytes32(loanId); - logs[0].data = ""; - } - - function _repayLog(address emitter, uint256 loanId, uint256 amount) - internal - pure - returns (EvmV1Decoder.LogEntry[] memory logs) - { - logs = new EvmV1Decoder.LogEntry[](1); - logs[0].address_ = emitter; - logs[0].topics = new bytes32[](2); - logs[0].topics[0] = REPAY_EVENT_SIGNATURE; - logs[0].topics[1] = bytes32(loanId); - logs[0].data = abi.encode(amount); - } -} +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ASCLoanManagerHarness} from "./harness/ASCLoanManagerHarness.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +/// @notice Regression tests for loan source-contract binding โ€” fund/repay logs must come from the registered source contract. +contract ASCLoanManagerSourceBindingTest is Test { + ASCLoanManagerHarness internal manager; + + bytes32 internal constant FUND_EVENT_SIGNATURE = + 0x9e71d2fb732e68272b7e74ecfd14638673c1d77e19a5d390a3ffff054d57c44b; + bytes32 internal constant REPAY_EVENT_SIGNATURE = + 0x040cee90ee4799897c30ca04e5feb6fa43dbba9b6d084b4b257cdafd84ba013e; + + address internal registeredSource = address(0xA11CE); + address internal spoofedSource = address(0xBAD); + + function setUp() public { + manager = new ASCLoanManagerHarness(); + manager.registerSourceLoanContract(registeredSource); + } + + function testFundLog_acceptsRegisteredEmitter() public view { + uint256 loanId = manager.exposeProcessFundLogs(_fundLog(registeredSource, 42)); + assertEq(loanId, 42); + } + + function testFundLog_rejectsUnregisteredSource() public { + ASCLoanManagerHarness fresh = new ASCLoanManagerHarness(); + vm.expectRevert("Source loan contract not registered!"); + fresh.exposeProcessFundLogs(_fundLog(registeredSource, 1)); + } + + function testFundLog_rejectsWrongEmitter() public { + vm.expectRevert("LoanFunded event not emitted by registered source loan contract!"); + manager.exposeProcessFundLogs(_fundLog(spoofedSource, 1)); + } + + function testRepayLog_acceptsRegisteredEmitter() public view { + (uint256 loanId, uint256 amount) = manager.exposeProcessRepayLogs(_repayLog(registeredSource, 7, 1000)); + assertEq(loanId, 7); + assertEq(amount, 1000); + } + + function testRepayLog_rejectsWrongEmitter() public { + vm.expectRevert("LoanRepaid event not emitted by registered source loan contract!"); + manager.exposeProcessRepayLogs(_repayLog(spoofedSource, 7, 1000)); + } + + function _fundLog(address emitter, uint256 loanId) internal pure returns (EvmV1Decoder.LogEntry[] memory logs) { + logs = new EvmV1Decoder.LogEntry[](1); + logs[0].address_ = emitter; + logs[0].topics = new bytes32[](2); + logs[0].topics[0] = FUND_EVENT_SIGNATURE; + logs[0].topics[1] = bytes32(loanId); + logs[0].data = ""; + } + + function _repayLog(address emitter, uint256 loanId, uint256 amount) + internal + pure + returns (EvmV1Decoder.LogEntry[] memory logs) + { + logs = new EvmV1Decoder.LogEntry[](1); + logs[0].address_ = emitter; + logs[0].topics = new bytes32[](2); + logs[0].topics[0] = REPAY_EVENT_SIGNATURE; + logs[0].topics[1] = bytes32(loanId); + logs[0].data = abi.encode(amount); + } +} diff --git a/loan/test/harness/ASCLoanManagerHarness.sol b/loan/test/harness/ASCLoanManagerHarness.sol index 34d69169..8afbc274 100644 --- a/loan/test/harness/ASCLoanManagerHarness.sol +++ b/loan/test/harness/ASCLoanManagerHarness.sol @@ -1,20 +1,20 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ASCLoanManager} from "../../contracts/sol/ASCLoanManager.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; - -/// @dev Test harness exposing internal log validators for source-contract binding tests. -contract ASCLoanManagerHarness is ASCLoanManager { - function exposeProcessFundLogs(EvmV1Decoder.LogEntry[] memory fundLogs) external view returns (uint256) { - return _processFundLogs(fundLogs); - } - - function exposeProcessRepayLogs(EvmV1Decoder.LogEntry[] memory repayLogs) - external - view - returns (uint256 loanId, uint256 amount) - { - return _processRepayLogs(repayLogs); - } -} +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ASCLoanManager} from "../../contracts/sol/ASCLoanManager.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +/// @dev Test harness exposing internal log validators for source-contract binding tests. +contract ASCLoanManagerHarness is ASCLoanManager { + function exposeProcessFundLogs(EvmV1Decoder.LogEntry[] memory fundLogs) external view returns (uint256) { + return _processFundLogs(fundLogs); + } + + function exposeProcessRepayLogs(EvmV1Decoder.LogEntry[] memory repayLogs) + external + view + returns (uint256 loanId, uint256 amount) + { + return _processRepayLogs(repayLogs); + } +} diff --git a/shared/submit_bridge_query.ts b/shared/submit_bridge_query.ts index f310ed5e..bf2e7ea8 100644 --- a/shared/submit_bridge_query.ts +++ b/shared/submit_bridge_query.ts @@ -1,91 +1,91 @@ -import dotenv from 'dotenv'; -import { Contract, ethers, InterfaceAbi } from 'ethers'; - -import ASCMinterABI from '../bridge/contracts/abi/ASCMinter.json'; -import { - generateProofFor, - submitProofToMinterAndAwait, - computeGasLimitForMinter, - isValidPrivateKey, - isValidContractAddress, -} from './utils'; - -dotenv.config({ override: true }); - -export type BridgeSubmitConfig = { - usageCommand: string; - exampleTxHash: string; - minterAddressEnvVar: 'ASC_MINTER_CONTRACT_ADDRESS' | 'ASC_CUSTOM_MINTER_CONTRACT_ADDRESS'; -}; - -export function printBridgeSubmitUsage(config: BridgeSubmitConfig): void { - console.error(` - Usage: - ${config.usageCommand} - - Example: - ${config.usageCommand} ${config.exampleTxHash} - `); -} - -export async function runBridgeSubmitQuery( - config: BridgeSubmitConfig, - transactionHash: string -): Promise { - if (!transactionHash.startsWith('0x') || transactionHash.length !== 66) { - throw new Error('Invalid transaction hash provided'); - } - - const ccNextPrivateKey = process.env.CREDITCOIN_WALLET_PRIVATE_KEY; - if (!isValidPrivateKey(ccNextPrivateKey)) { - throw new Error('CREDITCOIN_WALLET_PRIVATE_KEY environment variable is not configured or invalid'); - } - - const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); - if (!sourceChainKey) { - throw new Error('SOURCE_CHAIN_KEY environment variable is not configured or invalid'); - } - - const proofBuilderUrl = process.env.PROOF_BUILDER_URL; - if (!proofBuilderUrl) { - throw new Error('PROOF_BUILDER_URL is not configured or invalid'); - } - - const creditcoinRpcUrl = process.env.CREDITCOIN_RPC_URL; - if (!creditcoinRpcUrl) { - throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); - } - - const minterContractAddress = process.env[config.minterAddressEnvVar]; - if (!isValidContractAddress(minterContractAddress)) { - throw new Error(`${config.minterAddressEnvVar} is not configured or invalid`); - } - - const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; - if (!sourceChainRpcUrl) { - throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); - } - - const ccProvider = new ethers.JsonRpcProvider(creditcoinRpcUrl); - const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); - - const proofResult = await generateProofFor( - transactionHash, - sourceChainKey, - proofBuilderUrl, - ccProvider, - sourceChainProvider - ); - - if (!proofResult.success) { - throw new Error(`Failed to generate proof: ${proofResult.error}`); - } - - const wallet = new ethers.Wallet(ccNextPrivateKey!, ccProvider); - const contractABI = ASCMinterABI as unknown as InterfaceAbi; - const minterContract = new Contract(minterContractAddress!, contractABI, wallet); - - const proofData = proofResult.data!; - const gasLimit = await computeGasLimitForMinter(ccProvider, minterContract, proofData, wallet.address); - await submitProofToMinterAndAwait(minterContract, proofData, gasLimit); -} +import dotenv from 'dotenv'; +import { Contract, ethers, InterfaceAbi } from 'ethers'; + +import ASCMinterABI from '../bridge/contracts/abi/ASCMinter.json'; +import { + generateProofFor, + submitProofToMinterAndAwait, + computeGasLimitForMinter, + isValidPrivateKey, + isValidContractAddress, +} from './utils'; + +dotenv.config({ override: true }); + +export type BridgeSubmitConfig = { + usageCommand: string; + exampleTxHash: string; + minterAddressEnvVar: 'ASC_MINTER_CONTRACT_ADDRESS' | 'ASC_CUSTOM_MINTER_CONTRACT_ADDRESS'; +}; + +export function printBridgeSubmitUsage(config: BridgeSubmitConfig): void { + console.error(` + Usage: + ${config.usageCommand} + + Example: + ${config.usageCommand} ${config.exampleTxHash} + `); +} + +export async function runBridgeSubmitQuery( + config: BridgeSubmitConfig, + transactionHash: string +): Promise { + if (!transactionHash.startsWith('0x') || transactionHash.length !== 66) { + throw new Error('Invalid transaction hash provided'); + } + + const ccNextPrivateKey = process.env.CREDITCOIN_WALLET_PRIVATE_KEY; + if (!isValidPrivateKey(ccNextPrivateKey)) { + throw new Error('CREDITCOIN_WALLET_PRIVATE_KEY environment variable is not configured or invalid'); + } + + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + if (!sourceChainKey) { + throw new Error('SOURCE_CHAIN_KEY environment variable is not configured or invalid'); + } + + const proofBuilderUrl = process.env.PROOF_BUILDER_URL; + if (!proofBuilderUrl) { + throw new Error('PROOF_BUILDER_URL is not configured or invalid'); + } + + const creditcoinRpcUrl = process.env.CREDITCOIN_RPC_URL; + if (!creditcoinRpcUrl) { + throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); + } + + const minterContractAddress = process.env[config.minterAddressEnvVar]; + if (!isValidContractAddress(minterContractAddress)) { + throw new Error(`${config.minterAddressEnvVar} is not configured or invalid`); + } + + const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; + if (!sourceChainRpcUrl) { + throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); + } + + const ccProvider = new ethers.JsonRpcProvider(creditcoinRpcUrl); + const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); + + const proofResult = await generateProofFor( + transactionHash, + sourceChainKey, + proofBuilderUrl, + ccProvider, + sourceChainProvider + ); + + if (!proofResult.success) { + throw new Error(`Failed to generate proof: ${proofResult.error}`); + } + + const wallet = new ethers.Wallet(ccNextPrivateKey!, ccProvider); + const contractABI = ASCMinterABI as unknown as InterfaceAbi; + const minterContract = new Contract(minterContractAddress!, contractABI, wallet); + + const proofData = proofResult.data!; + const gasLimit = await computeGasLimitForMinter(ccProvider, minterContract, proofData, wallet.address); + await submitProofToMinterAndAwait(minterContract, proofData, gasLimit); +} From 969ef0cb2eceb9fae260bb153794a83988f0b181 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:10:21 -0600 Subject: [PATCH 05/31] Fix CI: resolve @gluwa/usc-contracts from npm package. --- .github/workflows/solidity.yml | 8 +------- VERIFICATION.md | 6 +++--- bridge/contracts/CONTRIBUTING.md | 2 +- bridge/contracts/abi-creator.sh | 0 bridge/contracts/sol/ASCMinter.sol | 2 +- bridge/foundry.toml | 6 ++---- bridge/test/ASCMinterBurnLog.t.sol | 2 +- bridge/test/harness/ASCMinterHarness.sol | 2 +- foundry.toml | 5 ++++- loan/contracts/abi-creator.sh | 1 + loan/contracts/sol/ASCLoanManager.sol | 4 ++-- loan/foundry.toml | 9 +++++---- loan/test/ASCLoanManagerSourceBinding.t.sol | 2 +- loan/test/harness/ASCLoanManagerHarness.sol | 2 +- 14 files changed, 24 insertions(+), 27 deletions(-) mode change 100644 => 100755 bridge/contracts/abi-creator.sh mode change 100644 => 100755 loan/contracts/abi-creator.sh diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index 32fec92c..e7403b79 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -17,13 +17,6 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Checkout usc-contracts (sibling for forge remappings) - uses: actions/checkout@v7 - with: - repository: gluwa/usc-contracts - path: ../usc-contracts - token: ${{ secrets.GITHUB_TOKEN }} - - name: Install foundry run: | export FOUNDRY_DIR="$HOME/.foundry" @@ -70,6 +63,7 @@ jobs: yarn install solc --version + chmod +x ./bridge/contracts/abi-creator.sh ./loan/contracts/abi-creator.sh ./bridge/contracts/abi-creator.sh ./loan/contracts/abi-creator.sh diff --git a/VERIFICATION.md b/VERIFICATION.md index eca6bf00..16cc4a81 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -4,11 +4,11 @@ Use this to validate the repository locally before release or deployment. ## Automated (CI + local) -From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH` and a sibling [`usc-contracts`](https://github.com/gluwa/usc-contracts) checkout at `../usc-contracts` on the **default branch**, which provides `contracts/common/EvmV1Decoder.sol` and `contracts/readability/ASCBase.sol`): +From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH`). Contracts resolve `EvmV1Decoder` from the yarn dependency `@gluwa/usc-contracts` (`contracts/decoding/`); loan `ASCBase` comes from the local bridge example: ```sh +yarn git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std # once -# sibling: ../usc-contracts should track usc-contracts main (or CI default branch) yarn verify ``` @@ -25,7 +25,7 @@ This runs: - Compiles bridge and loan contracts - Regenerates ABIs and fails on drift -- Runs `forge test` for bridge and loan (requires `usc-contracts` checkout + `forge-std`) +- Runs `forge test` for bridge and loan (requires `yarn install` + `forge-std`) ## Network preflight (optional, needs `.env`) diff --git a/bridge/contracts/CONTRIBUTING.md b/bridge/contracts/CONTRIBUTING.md index 236fb519..36b5651c 100644 --- a/bridge/contracts/CONTRIBUTING.md +++ b/bridge/contracts/CONTRIBUTING.md @@ -45,6 +45,6 @@ Ensure `--libraries` uses the deployed decoder address: --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: ``` -The library is published in npm at `contracts/decoding/EvmV1Decoder.sol` (deploy via `forge create` in tutorials). Solidity imports use `@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol` with a sibling `usc-contracts` checkout. +The library is published in npm at `contracts/decoding/EvmV1Decoder.sol`. Tutorials and Solidity imports both use that path via `@gluwa/usc-contracts` from `node_modules`. After deploy, submit proofs with `yarn hello_bridge:submit_query` or `yarn custom_bridge:submit_query`. diff --git a/bridge/contracts/abi-creator.sh b/bridge/contracts/abi-creator.sh old mode 100644 new mode 100755 diff --git a/bridge/contracts/sol/ASCMinter.sol b/bridge/contracts/sol/ASCMinter.sol index eb89ca1d..354e6b81 100644 --- a/bridge/contracts/sol/ASCMinter.sol +++ b/bridge/contracts/sol/ASCMinter.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import {ASCMintableToken, ASC_MINTER} from "./MintableToken.sol"; import {ASCBase} from "./ASCBase.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; contract ASCMinter is ASCBase { enum MinterActions { diff --git a/bridge/foundry.toml b/bridge/foundry.toml index a8bd05de..a59553ee 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -3,12 +3,10 @@ out = "out" src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] -# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts -# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. -allow_paths = ["../../usc-contracts"] +# Resolve @gluwa/usc-contracts from yarn-installed npm package (decoding/EvmV1Decoder). remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../../usc-contracts/", + "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" diff --git a/bridge/test/ASCMinterBurnLog.t.sol b/bridge/test/ASCMinterBurnLog.t.sol index f2668d2f..6f596b7a 100644 --- a/bridge/test/ASCMinterBurnLog.t.sol +++ b/bridge/test/ASCMinterBurnLog.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import {Test} from "forge-std/Test.sol"; import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; contract ASCMinterBurnLogTest is Test { ASCMinterHarness internal minter; diff --git a/bridge/test/harness/ASCMinterHarness.sol b/bridge/test/harness/ASCMinterHarness.sol index b57c34f5..eb44115b 100644 --- a/bridge/test/harness/ASCMinterHarness.sol +++ b/bridge/test/harness/ASCMinterHarness.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; contract ASCMinterHarness is ASCMinter { function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) diff --git a/foundry.toml b/foundry.toml index 8818a5f7..9942e91e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,8 +2,11 @@ out = "out" src = "bridge/contracts/sol" libs = ["node_modules"] +# Used when forge create is run from the repo root (example CI workflows). remappings = [ - "@gluwa/usc-contracts/=../usc-contracts/contracts/", + "@openzeppelin/=node_modules/@openzeppelin/", + "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/", + "@example/bridge/=bridge/contracts/sol/", ] solc_version = "0.8.30" optimizer = true diff --git a/loan/contracts/abi-creator.sh b/loan/contracts/abi-creator.sh old mode 100644 new mode 100755 index 8b167dec..02148a72 --- a/loan/contracts/abi-creator.sh +++ b/loan/contracts/abi-creator.sh @@ -11,6 +11,7 @@ for p in "$sol_directory"/*; do file_with_extension="$contract_name.json" solc --base-path . --include-path "node_modules" \ --allow-paths "bridge/contracts/sol" \ + "@example/bridge/=bridge/contracts/sol/" \ "$sol_directory/$file" \ --combined-json abi --overwrite --json-indent 2 | \ jq ".contracts[\"$p:$contract_name\"].abi // {}" > "$abi_directory/$file_with_extension" diff --git a/loan/contracts/sol/ASCLoanManager.sol b/loan/contracts/sol/ASCLoanManager.sol index a2ffb564..80e5fa7a 100644 --- a/loan/contracts/sol/ASCLoanManager.sol +++ b/loan/contracts/sol/ASCLoanManager.sol @@ -9,8 +9,8 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {LoanFlow, LoanStatus, LoanOrder, LoanTerms} from "./LoanTypes.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; -import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; +import {ASCBase} from "@example/bridge/ASCBase.sol"; /** * @title ASCLoanManager diff --git a/loan/foundry.toml b/loan/foundry.toml index a8bd05de..e77ac3c1 100644 --- a/loan/foundry.toml +++ b/loan/foundry.toml @@ -3,12 +3,13 @@ out = "out" src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] -# Forge remaps `@gluwa/usc-contracts` to a sibling checkout of gluwa/usc-contracts -# (default branch: contracts/common + contracts/readability). CI checks this out at ../usc-contracts. -allow_paths = ["../../usc-contracts"] +# Resolve @gluwa/usc-contracts from yarn-installed npm package (decoding/EvmV1Decoder). +# ASCBase comes from the local bridge example contracts. +allow_paths = ["../bridge"] remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../../usc-contracts/", + "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", + "@example/bridge/=../bridge/contracts/sol/", "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" diff --git a/loan/test/ASCLoanManagerSourceBinding.t.sol b/loan/test/ASCLoanManagerSourceBinding.t.sol index a1173388..7415dca0 100644 --- a/loan/test/ASCLoanManagerSourceBinding.t.sol +++ b/loan/test/ASCLoanManagerSourceBinding.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ASCLoanManagerHarness} from "./harness/ASCLoanManagerHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; /// @notice Regression tests for loan source-contract binding โ€” fund/repay logs must come from the registered source contract. contract ASCLoanManagerSourceBindingTest is Test { diff --git a/loan/test/harness/ASCLoanManagerHarness.sol b/loan/test/harness/ASCLoanManagerHarness.sol index 8afbc274..db2c8bc4 100644 --- a/loan/test/harness/ASCLoanManagerHarness.sol +++ b/loan/test/harness/ASCLoanManagerHarness.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {ASCLoanManager} from "../../contracts/sol/ASCLoanManager.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; /// @dev Test harness exposing internal log validators for source-contract binding tests. contract ASCLoanManagerHarness is ASCLoanManager { From dd68c1cf5c321dea02099d9f67f96ef4d9a737e7 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:43:18 -0600 Subject: [PATCH 06/31] fix package path for CI error --- .github/install-solidity-compiler.sh | 2 +- .github/workflows/javascript-checks.yaml | 3 +++ .github/workflows/solidity.yml | 12 +++++++++--- VERIFICATION.md | 12 ++++++------ bridge/README.md | 12 ++++++------ bridge/contracts/abi-creator.sh | 22 ++++++++++++++++++---- bridge/foundry.toml | 1 + bridge/hello-bridge/README.md | 16 ++++++++-------- loan/contracts/abi-creator.sh | 22 ++++++++++++++++++---- loan/foundry.toml | 2 +- shared/check_setup.ts | 17 ++++------------- shared/submit_bridge_query.ts | 5 +---- 12 files changed, 76 insertions(+), 50 deletions(-) diff --git a/.github/install-solidity-compiler.sh b/.github/install-solidity-compiler.sh index 091d8506..9c626a76 100755 --- a/.github/install-solidity-compiler.sh +++ b/.github/install-solidity-compiler.sh @@ -4,6 +4,6 @@ set -euo pipefail -curl -L https://github.com/ethereum/solidity/releases/download/v0.8.29/solc-static-linux > /usr/bin/solc +curl -L https://github.com/ethereum/solidity/releases/download/v0.8.30/solc-static-linux > /usr/bin/solc chmod a+x /usr/bin/solc diff --git a/.github/workflows/javascript-checks.yaml b/.github/workflows/javascript-checks.yaml index 6491479e..586d4522 100644 --- a/.github/workflows/javascript-checks.yaml +++ b/.github/workflows/javascript-checks.yaml @@ -40,6 +40,9 @@ jobs: echo "$HOME/.foundry/bin" >> "$GITHUB_PATH" + - name: Install forge-std + run: git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std + - name: Install dependencies run: | if [ -f yarn.lock ]; then diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index e7403b79..1950aad7 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -37,6 +37,12 @@ jobs: run: | yarn install + echo "INFO: forge remappings (bridge)" + ~/.foundry/bin/forge remappings --root bridge || true + echo "INFO: forge remappings (loan)" + ~/.foundry/bin/forge remappings --root loan || true + ls -la node_modules/@gluwa/usc-contracts/contracts || true + ~/.foundry/bin/forge build --root bridge ~/.foundry/bin/forge build --root loan ~/.foundry/bin/forge test --root bridge @@ -70,11 +76,11 @@ jobs: - name: Check for mismatch run: | echo "========== DEBUG DEBUG DEBUG ==========" - git status --short - git diff + git status --short -- bridge/contracts/abi loan/contracts/abi + git diff -- bridge/contracts/abi loan/contracts/abi echo "=======================================" - if [ -n "$(git status --short)" ]; then + if [ -n "$(git status --short -- bridge/contracts/abi loan/contracts/abi)" ]; then echo "FAIL: found ABI mismatch on disk vs. freshly generated from source" echo "TODO: update ABI on disk and commit the changes to git" exit 1 diff --git a/VERIFICATION.md b/VERIFICATION.md index 16cc4a81..8af83aab 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -14,12 +14,12 @@ yarn verify This runs: -| Step | Command | What it checks | -|------|---------|----------------| -| TypeScript | `yarn typecheck` | Tutorial scripts compile | -| Lint | `yarn eslint` | TS/JS style | -| Contracts | `yarn build` | `forge build` for bridge + loan | -| Unit tests | `forge test` | Loan source-contract binding + bridge burn-log parsing | +| Step | Command | What it checks | +| ---------- | ---------------- | ------------------------------------------------------ | +| TypeScript | `yarn typecheck` | Tutorial scripts compile | +| Lint | `yarn eslint` | TS/JS style | +| Contracts | `yarn build` | `forge build` for bridge + loan | +| Unit tests | `forge test` | Loan source-contract binding + bridge burn-log parsing | ### CI (`solidity.yml`) diff --git a/bridge/README.md b/bridge/README.md index 37c82b24..ab22268c 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -4,12 +4,12 @@ These tutorials implement a **simplified readability bridge** โ€” not the produc ## What this example uses -| Piece | Role | -|-------|------| -| **`ASCBase`** (local) | Verify a source-chain transaction via the native verifier precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. | -| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens once per proof. | -| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/decoding/`). Not the messaging bridge. | -| **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | +| Piece | Role | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | +| **`ASCBase`** (local) | Verify a source-chain transaction via the native verifier precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. | +| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens once per proof. | +| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/decoding/`). Not the messaging bridge. | +| **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint**. diff --git a/bridge/contracts/abi-creator.sh b/bridge/contracts/abi-creator.sh index b3dcfb2d..a9610e82 100755 --- a/bridge/contracts/abi-creator.sh +++ b/bridge/contracts/abi-creator.sh @@ -1,16 +1,30 @@ #!/bin/bash # WARNING: execute this from the repository root. +set -euo pipefail sol_directory="bridge/contracts/sol" abi_directory="bridge/contracts/abi" -for p in "$sol_directory"/*; do +extract_abi() { + local path="$1" + local name="$2" + jq -c --arg path "$path" --arg name "$name" ' + (.contracts // {}) as $c + | ($c[$path + ":" + $name].abi + // (($c | to_entries | map(select(.key | endswith(":" + $name))) | .[0].value.abi) // null) + // {}) + ' +} + +for p in "$sol_directory"/*.sol; do file=$(basename "$p") - contract_name="${file//.sol/}" + contract_name="${file%.sol}" file_with_extension="$contract_name.json" solc --base-path . --include-path "node_modules" \ - "$sol_directory/$file" \ + "@openzeppelin/=node_modules/@openzeppelin/" \ + "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/" \ + "$p" \ --combined-json abi --overwrite --json-indent 2 | \ - jq ".contracts[\"$p:$contract_name\"].abi // {}" > "$abi_directory/$file_with_extension" + extract_abi "$p" "$contract_name" | jq --indent 2 . > "$abi_directory/$file_with_extension" done diff --git a/bridge/foundry.toml b/bridge/foundry.toml index a59553ee..1543fd1e 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -4,6 +4,7 @@ src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] # Resolve @gluwa/usc-contracts from yarn-installed npm package (decoding/EvmV1Decoder). +allow_paths = ["../node_modules", "../lib"] remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", diff --git a/bridge/hello-bridge/README.md b/bridge/hello-bridge/README.md index 1ac27aa6..a6027938 100644 --- a/bridge/hello-bridge/README.md +++ b/bridge/hello-bridge/README.md @@ -13,14 +13,14 @@ bridging!** The flow is: Hello Bridge uses **shared tutorial contracts** already deployed on CC3 Testnet and Sepolia. The repository root [`.env`](../../.env) includes pre-filled values for: -| Variable | Purpose | -|----------|---------| -| `SOURCE_CHAIN_KEY` | Sepolia chain key on CC3 Testnet (`1`) | -| `PROOF_BUILDER_URL` | Proof builder for CC3 Testnet | -| `CREDITCOIN_RPC_URL` | CC3 Testnet RPC | -| `SOURCE_CHAIN_CONTRACT_ADDRESS` | Pre-deployed Sepolia burner ERC20 | -| `ASC_MINTER_CONTRACT_ADDRESS` | Pre-deployed `ASCMinter` on CC3 Testnet | -| `ASC_MINTABLE_TOKEN` | Pre-deployed wrapped token | +| Variable | Purpose | +| ------------------------------- | --------------------------------------- | +| `SOURCE_CHAIN_KEY` | Sepolia chain key on CC3 Testnet (`1`) | +| `PROOF_BUILDER_URL` | Proof builder for CC3 Testnet | +| `CREDITCOIN_RPC_URL` | CC3 Testnet RPC | +| `SOURCE_CHAIN_CONTRACT_ADDRESS` | Pre-deployed Sepolia burner ERC20 | +| `ASC_MINTER_CONTRACT_ADDRESS` | Pre-deployed `ASCMinter` on CC3 Testnet | +| `ASC_MINTABLE_TOKEN` | Pre-deployed wrapped token | Copy those lines into your own `.env` if starting fresh. Variable names are also listed in [bridge/.env.example](../.env.example). diff --git a/loan/contracts/abi-creator.sh b/loan/contracts/abi-creator.sh index 02148a72..268022be 100755 --- a/loan/contracts/abi-creator.sh +++ b/loan/contracts/abi-creator.sh @@ -1,18 +1,32 @@ #!/bin/bash # WARNING: execute this from the repository root. +set -euo pipefail sol_directory="loan/contracts/sol" abi_directory="loan/contracts/abi" -for p in "$sol_directory"/*; do +extract_abi() { + local path="$1" + local name="$2" + jq -c --arg path "$path" --arg name "$name" ' + (.contracts // {}) as $c + | ($c[$path + ":" + $name].abi + // (($c | to_entries | map(select(.key | endswith(":" + $name))) | .[0].value.abi) // null) + // {}) + ' +} + +for p in "$sol_directory"/*.sol; do file=$(basename "$p") - contract_name="${file//.sol/}" + contract_name="${file%.sol}" file_with_extension="$contract_name.json" solc --base-path . --include-path "node_modules" \ --allow-paths "bridge/contracts/sol" \ + "@openzeppelin/=node_modules/@openzeppelin/" \ + "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/" \ "@example/bridge/=bridge/contracts/sol/" \ - "$sol_directory/$file" \ + "$p" \ --combined-json abi --overwrite --json-indent 2 | \ - jq ".contracts[\"$p:$contract_name\"].abi // {}" > "$abi_directory/$file_with_extension" + extract_abi "$p" "$contract_name" | jq --indent 2 . > "$abi_directory/$file_with_extension" done diff --git a/loan/foundry.toml b/loan/foundry.toml index e77ac3c1..3f7c75aa 100644 --- a/loan/foundry.toml +++ b/loan/foundry.toml @@ -5,7 +5,7 @@ test = "test" libs = ["../node_modules", "../lib"] # Resolve @gluwa/usc-contracts from yarn-installed npm package (decoding/EvmV1Decoder). # ASCBase comes from the local bridge example contracts. -allow_paths = ["../bridge"] +allow_paths = ["../node_modules", "../lib", "../bridge"] remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", diff --git a/shared/check_setup.ts b/shared/check_setup.ts index a5a70127..f390a75f 100644 --- a/shared/check_setup.ts +++ b/shared/check_setup.ts @@ -101,10 +101,7 @@ function printResults(results: CheckResult[]): boolean { return allOk; } -async function checkChainKeyAttestation( - creditcoinRpc: string | undefined, - chainKey: number -): Promise { +async function checkChainKeyAttestation(creditcoinRpc: string | undefined, chainKey: number): Promise { if (!creditcoinRpc?.trim()) { return fail('Chain attestation: skipped (no CREDITCOIN_RPC_URL)'); } @@ -125,9 +122,7 @@ async function checkChainKeyAttestation( async function runNetworkChecks(): Promise { const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); const results: CheckResult[] = [ - sourceChainKey > 0 - ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) - : fail('SOURCE_CHAIN_KEY: missing or invalid'), + sourceChainKey > 0 ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) : fail('SOURCE_CHAIN_KEY: missing or invalid'), await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL), await checkProofBuilder(process.env.PROOF_BUILDER_URL), await checkVerifierPrecompile(process.env.CREDITCOIN_RPC_URL), @@ -141,9 +136,7 @@ async function runHelloBridgeChecks(): Promise { const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); results.push( - sourceChainKey > 0 - ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) - : fail('SOURCE_CHAIN_KEY: missing or invalid') + sourceChainKey > 0 ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) : fail('SOURCE_CHAIN_KEY: missing or invalid') ); results.push( @@ -181,9 +174,7 @@ async function runBridgeChecks(): Promise { const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); results.push( - sourceChainKey > 0 - ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) - : fail('SOURCE_CHAIN_KEY: missing or invalid') + sourceChainKey > 0 ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) : fail('SOURCE_CHAIN_KEY: missing or invalid') ); results.push(await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL)); diff --git a/shared/submit_bridge_query.ts b/shared/submit_bridge_query.ts index bf2e7ea8..39f30f08 100644 --- a/shared/submit_bridge_query.ts +++ b/shared/submit_bridge_query.ts @@ -28,10 +28,7 @@ export function printBridgeSubmitUsage(config: BridgeSubmitConfig): void { `); } -export async function runBridgeSubmitQuery( - config: BridgeSubmitConfig, - transactionHash: string -): Promise { +export async function runBridgeSubmitQuery(config: BridgeSubmitConfig, transactionHash: string): Promise { if (!transactionHash.startsWith('0x') || transactionHash.length !== 66) { throw new Error('Invalid transaction hash provided'); } From 67cbf59f8fd41961013c319e027cbe24fd999f38 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:22:09 -0600 Subject: [PATCH 07/31] Fix outstanding issues --- .github/workflows/bridge-offchain-worker.yml | 8 +- .../workflows/custom-contracts-bridging.yml | 8 +- .github/workflows/hello-bridge.yml | 8 +- .github/workflows/javascript-checks.yaml | 2 +- .github/workflows/loan-flow.yml | 8 +- .github/workflows/solidity.yml | 15 +- .gitignore | 2 + .lycheeignore | 2 + README.md | 15 +- VERIFICATION.md | 18 +- bridge/.env.example | 2 +- bridge/README.md | 26 +- bridge/bridge-offchain-worker/README.md | 6 +- bridge/bridge-offchain-worker/worker.ts | 2 +- .../{CONTRIBUTING.md => Contributor Notes.md} | 8 +- bridge/contracts/{DEPLOY.md => DEPLOYMENT.md} | 2 +- bridge/contracts/abi/ASCBase.json | 100 ------- bridge/contracts/abi/ASCMinter.json | 38 +++ bridge/contracts/abi/VerifierInterface.json | 1 - bridge/contracts/abi_generation.md | 2 +- bridge/contracts/sol/ASCBase.sol | 88 ------- bridge/contracts/sol/ASCMinter.sol | 47 +++- bridge/contracts/sol/VerifierInterface.sol | 39 --- bridge/custom-contracts-bridging/README.md | 21 +- bridge/foundry.toml | 8 +- bridge/hello-bridge/README.md | 4 +- bridge/test/ASCMinterBurnLog.t.sol | 2 +- bridge/test/ASCMinterSecurity.t.sol | 54 ++++ bridge/test/harness/ASCMinterHarness.sol | 16 +- foundry.toml | 1 - loan/contracts/abi-creator.sh | 2 - loan/contracts/abi/ASCLoanManager.json | 5 - loan/contracts/abi_generation.md | 2 +- loan/contracts/sol/ASCLoanManager.sol | 8 +- loan/foundry.toml | 12 +- loan/scripts/README.md | 8 +- loan/scripts/fund_loan.ts | 2 +- loan/scripts/repay_loan.ts | 2 +- loan/test/ASCLoanManagerSourceBinding.t.sol | 2 +- loan/test/harness/ASCLoanManagerHarness.sol | 2 +- package.json | 2 +- shared/check_setup.ts | 142 +++++++++- shared/contracts/abi-creator.sh | 31 +++ {loan => shared}/contracts/abi/TestERC20.json | 0 {loan => shared}/contracts/sol/TestERC20.sol | 5 +- shared/utils/index.ts | 4 +- yarn.lock | 245 ++++++++++++++---- 47 files changed, 628 insertions(+), 399 deletions(-) rename bridge/contracts/{CONTRIBUTING.md => Contributor Notes.md} (64%) rename bridge/contracts/{DEPLOY.md => DEPLOYMENT.md} (70%) delete mode 100644 bridge/contracts/abi/ASCBase.json delete mode 100644 bridge/contracts/abi/VerifierInterface.json delete mode 100644 bridge/contracts/sol/ASCBase.sol delete mode 100644 bridge/contracts/sol/VerifierInterface.sol create mode 100644 bridge/test/ASCMinterSecurity.t.sol create mode 100644 shared/contracts/abi-creator.sh rename {loan => shared}/contracts/abi/TestERC20.json (100%) rename {loan => shared}/contracts/sol/TestERC20.sol (87%) diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index ba4ee7fc..43c4475d 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -166,7 +166,7 @@ jobs: - name: Setup id: setup run: | - yarn install + yarn install --ignore-engines SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + shared/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -217,7 +217,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index 08957ddb..759c240c 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -166,7 +166,7 @@ jobs: - name: Setup id: setup run: | - yarn install + yarn install --ignore-engines SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + shared/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -221,7 +221,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index a994a2f5..8ca8f255 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -166,7 +166,7 @@ jobs: - name: Setup id: setup run: | - yarn install + yarn install --ignore-engines SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key ${{ steps.env.outputs.source_chain_private_key }} \ - loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + shared/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -208,7 +208,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -217,7 +217,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) echo "$OUTPUT" ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/javascript-checks.yaml b/.github/workflows/javascript-checks.yaml index 586d4522..6fc53533 100644 --- a/.github/workflows/javascript-checks.yaml +++ b/.github/workflows/javascript-checks.yaml @@ -46,7 +46,7 @@ jobs: - name: Install dependencies run: | if [ -f yarn.lock ]; then - yarn install + yarn install --ignore-engines else npm clean-install fi diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index fe1ff0dd..404e2f3c 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -166,7 +166,7 @@ jobs: - name: Setup id: setup run: | - yarn install + yarn install --ignore-engines SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -198,7 +198,7 @@ jobs: --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ - loan/contracts/sol/TestERC20.sol:TestERC20 2>&1) + shared/contracts/sol/TestERC20.sol:TestERC20 2>&1) echo "$OUTPUT" SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" @@ -218,7 +218,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -227,7 +227,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager 2>&1) echo "$OUTPUT" ASC_LOAN_MANAGER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index 1950aad7..48262a05 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -35,13 +35,15 @@ jobs: - name: Compile and test smart contracts run: | - yarn install + yarn install --ignore-engines echo "INFO: forge remappings (bridge)" ~/.foundry/bin/forge remappings --root bridge || true echo "INFO: forge remappings (loan)" ~/.foundry/bin/forge remappings --root loan || true ls -la node_modules/@gluwa/usc-contracts/contracts || true + ls -la node_modules/@gluwa/usc-contracts/contracts/readability || true + ls -la node_modules/@gluwa/usc-contracts/contracts/common || true ~/.foundry/bin/forge build --root bridge ~/.foundry/bin/forge build --root loan @@ -66,21 +68,22 @@ jobs: - name: Regenerate the ABI run: | - yarn install + yarn install --ignore-engines solc --version - chmod +x ./bridge/contracts/abi-creator.sh ./loan/contracts/abi-creator.sh + chmod +x ./bridge/contracts/abi-creator.sh ./loan/contracts/abi-creator.sh ./shared/contracts/abi-creator.sh ./bridge/contracts/abi-creator.sh ./loan/contracts/abi-creator.sh + ./shared/contracts/abi-creator.sh - name: Check for mismatch run: | echo "========== DEBUG DEBUG DEBUG ==========" - git status --short -- bridge/contracts/abi loan/contracts/abi - git diff -- bridge/contracts/abi loan/contracts/abi + git status --short -- bridge/contracts/abi loan/contracts/abi shared/contracts/abi + git diff -- bridge/contracts/abi loan/contracts/abi shared/contracts/abi echo "=======================================" - if [ -n "$(git status --short -- bridge/contracts/abi loan/contracts/abi)" ]; then + if [ -n "$(git status --short -- bridge/contracts/abi loan/contracts/abi shared/contracts/abi)" ]; then echo "FAIL: found ABI mismatch on disk vs. freshly generated from source" echo "TODO: update ABI on disk and commit the changes to git" exit 1 diff --git a/.gitignore b/.gitignore index 9ac1caf5..225cdab9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ megalinter-reports/ # Local secrets โ€” copy from .env.example / .env.devnet.example (see README) .env .env.devnet + +.tools/ diff --git a/.lycheeignore b/.lycheeignore index 0c601727..5a2bdabf 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -24,3 +24,5 @@ https://github.com/gluwa/creditcoin3/pull(.*) https://developer.metamask.io(.*) https://(.*).creditcoin.network/ https://blockchain.googleapis.com/v1/projects/(.*) +https://github.com/gluwa/usc-contracts(.*) +https://docs.creditcoin.org/attestcoin-protocol(.*) diff --git a/README.md b/README.md index 1e07c684..371874da 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # ๐ŸŒ‰ ASC Testnet Bridge Examples ๐ŸŒ‰ -This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCBase` + `ASCMinter`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). +This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCMinter` on shared package `ASCBase`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). + +`@gluwa/usc-contracts` is resolved from [`asc-contracts`](https://github.com/gluwa/asc-contracts) (`git+https`), which provides one shared readability `ASCBase` for bridge and loan. Learn how to use the Creditcoin Decentralized Oracle through guided tutorials where you set up and interact with your own decentralized bridge and loan examples. @@ -8,15 +10,17 @@ Learn how to use the Creditcoin Decentralized Oracle through guided tutorials wh ``` usc-testnet-bridge-examples/ -โ”œโ”€โ”€ bridge/ # Simplified ASC bridge tutorials + contracts (ASCBase, ASCMinter) -โ”‚ โ”œโ”€โ”€ contracts/ # Local readability contracts; EvmV1Decoder lib from @gluwa/usc-contracts +โ”œโ”€โ”€ bridge/ # Simplified ASC bridge tutorials + ASCMinter (ASCBase from @gluwa/usc-contracts) +โ”‚ โ”œโ”€โ”€ contracts/ # Example ASC apps; ASCBase + EvmV1Decoder from @gluwa/usc-contracts โ”‚ โ”œโ”€โ”€ hello-bridge/ โ”‚ โ”œโ”€โ”€ custom-contracts-bridging/ โ”‚ โ””โ”€โ”€ bridge-offchain-worker/ โ”œโ”€โ”€ loan/ # Cross-chain loan tutorial + loan contracts -โ”‚ โ”œโ”€โ”€ contracts/ # ASCLoanManager, AuxiliaryLoanContract, TestERC20, โ€ฆ +โ”‚ โ”œโ”€โ”€ contracts/ # ASCLoanManager, AuxiliaryLoanContract, โ€ฆ โ”‚ โ””โ”€โ”€ scripts/ # Loan flow CLI scripts -โ””โ”€โ”€ shared/utils/ # Proof builder + submit helpers shared by both examples +โ”œโ”€โ”€ shared/ +โ”‚ โ”œโ”€โ”€ contracts/ # TestERC20 (shared Sepolia burner for bridge + loan) +โ”‚ โ””โ”€โ”€ utils/ # Proof builder + submit helpers shared by both examples ``` Package overview: [bridge/README.md](./bridge/README.md) (simplified bridge model and what is out of scope). @@ -56,6 +60,7 @@ Then source your env file and verify connectivity: source .env yarn utils:check_setup hello # Hello Bridge yarn utils:check_setup bridge # after custom-contracts deploy +yarn utils:check_setup loan # after loan stack deploy ``` Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ADDRESS` in `.env` and reuse for the loan tutorial. diff --git a/VERIFICATION.md b/VERIFICATION.md index 8af83aab..6f540277 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -4,7 +4,7 @@ Use this to validate the repository locally before release or deployment. ## Automated (CI + local) -From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH`). Contracts resolve `EvmV1Decoder` from the yarn dependency `@gluwa/usc-contracts` (`contracts/decoding/`); loan `ASCBase` comes from the local bridge example: +From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH`). Bridge and loan both inherit readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts` (`contracts/readability/`, `contracts/common/`): ```sh yarn @@ -14,12 +14,12 @@ yarn verify This runs: -| Step | Command | What it checks | -| ---------- | ---------------- | ------------------------------------------------------ | -| TypeScript | `yarn typecheck` | Tutorial scripts compile | -| Lint | `yarn eslint` | TS/JS style | -| Contracts | `yarn build` | `forge build` for bridge + loan | -| Unit tests | `forge test` | Loan source-contract binding + bridge burn-log parsing | +| Step | Command | What it checks | +| ---------- | ---------------- | ---------------------------------------------------------------------------------------- | +| TypeScript | `yarn typecheck` | Tutorial scripts compile | +| Lint | `yarn eslint` | TS/JS style | +| Contracts | `yarn build` | `forge build` for bridge + loan | +| Unit tests | `forge test` | Loan source-contract binding; bridge burn-log parsing + emitter whitelist / query dedupe | ### CI (`solidity.yml`) @@ -36,6 +36,7 @@ source .env yarn utils:check_setup network # RPC + attestation only yarn utils:check_setup hello # + hello-bridge contract addresses yarn utils:check_setup bridge # + wallet key for custom bridge +yarn utils:check_setup loan # + loan contracts and lender/borrower keys ``` ## Manual end-to-end โ€” Hello Bridge @@ -58,9 +59,10 @@ Tokens minted! Contract: 0x..., To: 0x..., Amount: 50000000000000000000, QueryId ## Manual end-to-end โ€” Custom bridge + loan - [ ] Deploy `EvmV1Decoder` once; set `EVM_V1_DECODER_LIBRARY_ADDRESS` -- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` +- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` (whitelists the Sepolia burn emitter) - [ ] Burn โ†’ `yarn custom_bridge:submit_query` โ†’ mint succeeds once - [ ] Second submit with same proof reverts (`Query already processed`) +- [ ] Proof of a burn from a non-whitelisted emitter reverts (`Emitter not whitelisted`) โ€” also covered by `forge test --root bridge` (`ASCMinterSecurity.t.sol`) - [ ] Deploy loan stack; `yarn loan_flow:register_source_contract` - [ ] Fund/repay on Sepolia; worker or manual proof updates loan status - [ ] Spoofed `LoanFunded` from unregistered contract cannot mark loan funded (covered by unit tests) diff --git a/bridge/.env.example b/bridge/.env.example index 79cf789a..2310a00f 100644 --- a/bridge/.env.example +++ b/bridge/.env.example @@ -7,7 +7,7 @@ SOURCE_CHAIN_KEY=1 PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" SOURCE_CHAIN_CONTRACT_ADDRESS="0x0F24FD9e0524BA53d3f0A4A40350Adf5370b4A53" ASC_MINTER_CONTRACT_ADDRESS="0x2Be9B8640ED32815d3B9e8C92AbcD3F15F07396f" -ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" +ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" # betterleaks:allow CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" ## User-provided diff --git a/bridge/README.md b/bridge/README.md index ab22268c..1ad9d3b0 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -1,19 +1,21 @@ # Bridge examples (simplified ASC bridge) -These tutorials implement a **simplified readability bridge** โ€” not the production [write-ability](https://github.com/gluwa/usc-contracts/tree/main/contracts/write-ability) stack (Outbox, Relayer, Inbox, liquidity operators, etc.). +These tutorials implement a **simplified readability bridge** โ€” not the production write-ability stack (Outbox, Relayer, Inbox, liquidity operators, etc.) published as [`@gluwa/usc-contracts`](https://www.npmjs.com/package/@gluwa/usc-contracts). + +This example follows **[Attestcoin Readability โ€” Building a Bridge Minter (ยง6.4)](https://docs.attestcoin.org/attestcoin-protocol/attestcoin-readability)** (inherit `ASCBase`, prove a source-chain burn, mint wrapped tokens on Creditcoin). ## What this example uses -| Piece | Role | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | -| **`ASCBase`** (local) | Verify a source-chain transaction via the native verifier precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. | -| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens once per proof. | -| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/decoding/`). Not the messaging bridge. | -| **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | +| Piece | Role | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | +| **`ASCBase`** (from `@gluwa/usc-contracts`) | Shared readability base: verify via native precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. Same base as the loan example. | +| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens **only if the source-chain emitter is whitelisted** via `wrapOriginToken`. | +| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/common/`). Not the messaging bridge. | +| **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | -Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint**. +Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint** (mint requires the burn contract address to be a whitelisted emitter). -Anyone with a valid proof can submit `execute`; the offchain worker is optional UX automation. +Anyone with a valid proof can submit `execute`, but the ASC still rejects burns from emitters that were never registered; the offchain worker is optional UX automation. ## What this example does not use @@ -21,13 +23,13 @@ Anyone with a valid proof can submit `execute`; the offchain worker is optional - `ASCBridgeLiquidityOperator`, `ClientBridgeLiquidityOperator`, or other write-ability bridge contracts - Trusted relayers or operator-gated mint paths -For the production bridge architecture, see [usc-contracts](https://github.com/gluwa/usc-contracts) and Creditcoin's write-ability documentation. +For the production bridge architecture, see [`@gluwa/usc-contracts`](https://www.npmjs.com/package/@gluwa/usc-contracts) and Creditcoin's write-ability documentation. ## Layout ``` bridge/ -โ”œโ”€โ”€ contracts/sol/ ASCBase, ASCMinter, MintableToken, BridgeTestToken +โ”œโ”€โ”€ contracts/sol/ ASCMinter, MintableToken, BridgeTestToken (ASCBase from package) โ”œโ”€โ”€ hello-bridge/ Pre-deployed ASC + burner (minimal setup) โ”œโ”€โ”€ custom-contracts-bridging/ Deploy your own ASC + wrapped token โ””โ”€โ”€ bridge-offchain-worker/ Automate proof submission after burns @@ -39,7 +41,7 @@ bridge/ 2. [Custom Contracts Bridging](./custom-contracts-bridging/README.md) 3. [Bridge Offchain Worker](./bridge-offchain-worker/README.md) -Deployment troubleshooting: [contracts/CONTRIBUTING.md](./contracts/CONTRIBUTING.md). +Deployment troubleshooting: [contracts/Contributor Notes.md](./contracts/Contributor%20Notes.md). ## Before you run tutorials diff --git a/bridge/bridge-offchain-worker/README.md b/bridge/bridge-offchain-worker/README.md index 9c687b9c..d6eac548 100644 --- a/bridge/bridge-offchain-worker/README.md +++ b/bridge/bridge-offchain-worker/README.md @@ -1,9 +1,13 @@ # Bridge Offchain Worker +Optional UX automation for the custom bridge: users only burn on Sepolia; the worker finishes the mint on Creditcoin so they do not run `yarn custom_bridge:submit_query` by hand. It is not required for security โ€” anyone with a valid proof can still call `ASCMinter.execute` manually, but the ASC still only mints for whitelisted source-chain emitters. + +**Flow:** poll Sepolia for `TokensBurnedForBridging` โ†’ wait for attestation and fetch a USC proof from the Proof Builder โ†’ submit that proof via `ASCMinter.execute` on CC3 Testnet โ†’ log `TokensMinted` on Creditcoin (requires the burn contract to already be registered with `wrapOriginToken`). Same burn โ†’ proof โ†’ mint path as the tutorial, without a write-ability Relayer. + > [!NOTE] > **Start here:** [Custom Contract Bridging ยง7 โ€” Automate proof submission](../custom-contracts-bridging/README.md#7-automate-proof-submission-optional) for the integrated tutorial step. -This folder contains the worker implementation (`worker.ts`). It watches Sepolia for burns and calls `ASCMinter.execute` on CC3 Testnet โ€” same simplified bridge as the manual `yarn custom_bridge:submit_query` path, with no write-ability Relayer. +Implementation: `worker.ts`. ## Quick start diff --git a/bridge/bridge-offchain-worker/worker.ts b/bridge/bridge-offchain-worker/worker.ts index b2a24362..ea63ce33 100644 --- a/bridge/bridge-offchain-worker/worker.ts +++ b/bridge/bridge-offchain-worker/worker.ts @@ -1,7 +1,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; -import burnerAbi from '../../loan/contracts/abi/TestERC20.json'; +import burnerAbi from '../../shared/contracts/abi/TestERC20.json'; import ASCMinterABI from '../contracts/abi/ASCMinter.json'; import { generateProofFor, diff --git a/bridge/contracts/CONTRIBUTING.md b/bridge/contracts/Contributor Notes.md similarity index 64% rename from bridge/contracts/CONTRIBUTING.md rename to bridge/contracts/Contributor Notes.md index 36b5651c..0b052d4b 100644 --- a/bridge/contracts/CONTRIBUTING.md +++ b/bridge/contracts/Contributor Notes.md @@ -13,7 +13,9 @@ yarn For loan contracts: `./loan/contracts/abi-creator.sh`. -Bridge contracts are local simplified readability contracts (`ASCBase`, `ASCMinter`). Only `EvmV1Decoder` is imported from `@gluwa/usc-contracts`. +Bridge example contracts (`ASCMinter`, tokens) inherit shared readability `ASCBase` from `@gluwa/usc-contracts` (`contracts/readability/ASCBase.sol`). `EvmV1Decoder` comes from the same package (`contracts/common/`). + +`ASCMinter` mints only when the burn logโ€™s emitting address is in `whitelistedEmitters` (set by `wrapOriginToken`). Regenerate ABIs after changing that surface. ## Deploy troubleshooting @@ -42,9 +44,9 @@ cast balance --rpc-url $CREDITCOIN_RPC_URL Ensure `--libraries` uses the deployed decoder address: ```bash ---libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder: +--libraries node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder: ``` -The library is published in npm at `contracts/decoding/EvmV1Decoder.sol`. Tutorials and Solidity imports both use that path via `@gluwa/usc-contracts` from `node_modules`. +The library is published in npm at `contracts/common/EvmV1Decoder.sol`. Tutorials and Solidity imports both use that path via `@gluwa/usc-contracts` from `node_modules`. After deploy, submit proofs with `yarn hello_bridge:submit_query` or `yarn custom_bridge:submit_query`. diff --git a/bridge/contracts/DEPLOY.md b/bridge/contracts/DEPLOYMENT.md similarity index 70% rename from bridge/contracts/DEPLOY.md rename to bridge/contracts/DEPLOYMENT.md index bf5dd1e2..0a32b1db 100644 --- a/bridge/contracts/DEPLOY.md +++ b/bridge/contracts/DEPLOYMENT.md @@ -1,5 +1,5 @@ # Deployment troubleshooting -Moved to [CONTRIBUTING.md](./CONTRIBUTING.md). +Moved to [Contributor Notes.md](./Contributor%20Notes.md). Tutorial deploy steps live in [custom-contracts-bridging/README.md](../custom-contracts-bridging/README.md). diff --git a/bridge/contracts/abi/ASCBase.json b/bridge/contracts/abi/ASCBase.json deleted file mode 100644 index 9249387e..00000000 --- a/bridge/contracts/abi/ASCBase.json +++ /dev/null @@ -1,100 +0,0 @@ -[ - { - "inputs": [], - "name": "VERIFIER", - "outputs": [ - { - "internalType": "contract INativeQueryVerifier", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "action", - "type": "uint8" - }, - { - "internalType": "uint64", - "name": "chainKey", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "blockHeight", - "type": "uint64" - }, - { - "internalType": "bytes", - "name": "encodedTransaction", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "merkleRoot", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "hash", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "isLeft", - "type": "bool" - } - ], - "internalType": "struct INativeQueryVerifier.MerkleProofEntry[]", - "name": "siblings", - "type": "tuple[]" - }, - { - "internalType": "bytes32", - "name": "lowerEndpointDigest", - "type": "bytes32" - }, - { - "internalType": "bytes32[]", - "name": "continuityRoots", - "type": "bytes32[]" - } - ], - "name": "execute", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "processedQueries", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/bridge/contracts/abi/ASCMinter.json b/bridge/contracts/abi/ASCMinter.json index 33dd2d9c..3e40daa8 100644 --- a/bridge/contracts/abi/ASCMinter.json +++ b/bridge/contracts/abi/ASCMinter.json @@ -10,6 +10,25 @@ "name": "InvalidAction", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "emitter", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "EmitterWhitelisted", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -152,6 +171,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelistedEmitters", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/bridge/contracts/abi/VerifierInterface.json b/bridge/contracts/abi/VerifierInterface.json deleted file mode 100644 index 0967ef42..00000000 --- a/bridge/contracts/abi/VerifierInterface.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/bridge/contracts/abi_generation.md b/bridge/contracts/abi_generation.md index 76ccd252..c0948825 100644 --- a/bridge/contracts/abi_generation.md +++ b/bridge/contracts/abi_generation.md @@ -1,3 +1,3 @@ # ABI generation -Moved to [CONTRIBUTING.md](./CONTRIBUTING.md). +Moved to [Contributor Notes.md](./Contributor%20Notes.md). diff --git a/bridge/contracts/sol/ASCBase.sol b/bridge/contracts/sol/ASCBase.sol deleted file mode 100644 index 487af21a..00000000 --- a/bridge/contracts/sol/ASCBase.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {INativeQueryVerifier, NativeQueryVerifierLib} from "./VerifierInterface.sol"; - -abstract contract ASCBase { - /// @notice The Native Query Verifier precompile instance - /// @dev Address: 0x0000000000000000000000000000000000000FD2 (4050 decimal) - INativeQueryVerifier public immutable VERIFIER; - - mapping(bytes32 => bool) public processedQueries; - - constructor() { - // Get the precompile instance using the helper library - VERIFIER = NativeQueryVerifierLib.getVerifier(); - } - - function _processAndEmitEvent(uint8 action, bytes32 queryId, bytes memory encodedTransaction) internal virtual; - - function execute( - uint8 action, - uint64 chainKey, - uint64 blockHeight, - bytes calldata encodedTransaction, - bytes32 merkleRoot, - INativeQueryVerifier.MerkleProofEntry[] calldata siblings, - bytes32 lowerEndpointDigest, - bytes32[] calldata continuityRoots - ) external returns (bool success) { - bytes32 queryId = _computeQueryId(chainKey, blockHeight, merkleRoot, siblings); - - require(!processedQueries[queryId], "Query already processed"); - - // First we verify the proof - bool verified = _verifyProof( - chainKey, blockHeight, encodedTransaction, merkleRoot, siblings, lowerEndpointDigest, continuityRoots - ); - require(verified, "Proof of inclusion verification failed"); - - // Mark the query as processed - processedQueries[queryId] = true; - - _processAndEmitEvent(action, queryId, encodedTransaction); - - return true; - } - - function _verifyProof( - uint64 chainKey, - uint64 blockHeight, - bytes calldata encodedTransaction, - bytes32 merkleRoot, - INativeQueryVerifier.MerkleProofEntry[] calldata siblings, - bytes32 lowerEndpointDigest, - bytes32[] calldata continuityRoots - ) internal returns (bool verified) { - INativeQueryVerifier.MerkleProof memory merkleProof = - INativeQueryVerifier.MerkleProof({root: merkleRoot, siblings: siblings}); - - INativeQueryVerifier.ContinuityProof memory continuityProof = - INativeQueryVerifier.ContinuityProof({lowerEndpointDigest: lowerEndpointDigest, roots: continuityRoots}); - - // Verify inclusion proof - verified = VERIFIER.verifyAndEmit(chainKey, blockHeight, encodedTransaction, merkleProof, continuityProof); - - return verified; - } - - function _computeQueryId( - uint64 chainKey, - uint64 blockHeight, - bytes32 merkleRoot, - INativeQueryVerifier.MerkleProofEntry[] calldata siblings - ) internal view returns (bytes32 queryId) { - INativeQueryVerifier.MerkleProof memory merkle_proof = - INativeQueryVerifier.MerkleProof({ root: merkleRoot, siblings: siblings }); - - uint256 txIndex = VERIFIER.calculateTxIndex(merkle_proof); - - assembly { - let ptr := mload(0x40) - mstore(ptr, chainKey) - mstore(add(ptr, 32), shl(192, blockHeight)) - mstore(add(ptr, 40), txIndex) - queryId := keccak256(ptr, 72) - } - } -} diff --git a/bridge/contracts/sol/ASCMinter.sol b/bridge/contracts/sol/ASCMinter.sol index 354e6b81..40f12bde 100644 --- a/bridge/contracts/sol/ASCMinter.sol +++ b/bridge/contracts/sol/ASCMinter.sol @@ -2,12 +2,19 @@ pragma solidity ^0.8.23; import {ASCMintableToken, ASC_MINTER} from "./MintableToken.sol"; -import {ASCBase} from "./ASCBase.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; - +import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; + +/** + * @title ASCMinter + * @notice Simplified ASC that mints wrapped tokens after a proved source-chain burn. + * @dev Mints only when the burn log's emitting contract (source-chain token) is whitelisted + * via {wrapOriginToken}. Unregistered emitters are rejected even if the proof is valid. + */ contract ASCMinter is ASCBase { enum MinterActions { - Mint // 0 + None, // 0 โ€” holder + Mint // 1 } error InvalidAction(uint8 action); @@ -16,9 +23,19 @@ contract ASCMinter is ASCBase { 0x17dc4d6f69d484e59be774c29b47d2fa4c14af2e01df42fc5643ac968f4d427e; event TokensMinted(address indexed wrappedTokenAddress, address indexed burntFrom, uint256 amount, bytes32 indexed queryId); + event EmitterWhitelisted(address indexed emitter, address indexed wrappedToken); + + /// @notice Source-chain burn contracts allowed to trigger mints (log `address` / emitter). + mapping(address => bool) public whitelistedEmitters; + /// @notice Whitelisted emitter โ†’ wrapped ASCMintableToken on Creditcoin. mapping(address => address) public wrappedTokens; + /** + * @notice Whitelist a source-chain burn emitter and map it to a Creditcoin wrapped token. + * @param originToken Source-chain ERC20 that emits `TokensBurnedForBridging` (the emitter). + * @param targetToken Creditcoin ASCMintableToken that this ASC may mint. + */ function wrapOriginToken(address originToken, address targetToken) external { require(originToken != address(0), "Origin token cannot be the zero address"); require(targetToken != address(0), "Target token cannot be the zero address"); @@ -26,7 +43,10 @@ contract ASCMinter is ASCBase { require(ASCMintableToken(targetToken).owner() == msg.sender, "Target token must be owned by the caller"); require(ASCMintableToken(targetToken).hasRole(ASC_MINTER, address(this)), "Target token must be ASCMintableToken and support AccessControl"); + whitelistedEmitters[originToken] = true; wrappedTokens[originToken] = targetToken; + + emit EmitterWhitelisted(originToken, targetToken); } function _processAndEmitEvent(uint8 action, bytes32 queryId, bytes memory encodedTransaction) internal override { @@ -50,15 +70,13 @@ contract ASCMinter is ASCBase { EvmV1Decoder.getLogsByEventSignature(receipt, BURN_EVENT_SIGNATURE); require(burnLogs.length > 0, "No burn events found"); - // Check if the burn is valid - (address originTokenAddress, address burntFrom, uint256 burntValue) = _processBurnLogs(burnLogs); + (address emitter, address burntFrom, uint256 burntValue) = _processBurnLogs(burnLogs); - address wrappedTokenAddress = wrappedTokens[originTokenAddress]; + // Reject burns from contracts that were never whitelisted, even with a valid proof. + require(whitelistedEmitters[emitter], "Emitter not whitelisted"); - require( - wrappedTokenAddress != address(0), - "Origin token not registered in wrapped tokens!" - ); + address wrappedTokenAddress = wrappedTokens[emitter]; + require(wrappedTokenAddress != address(0), "No wrapped token for emitter"); ASCMintableToken(wrappedTokenAddress).mint(burntFrom, burntValue); @@ -68,7 +86,7 @@ contract ASCMinter is ASCBase { function _processBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) internal pure - returns (address originTokenAddress, address from, uint256 value) + returns (address emitter, address from, uint256 value) { // For this demonstration we only process the first burn log found within a transaction. // We only expect a single burn log per transaction in this demo anyways @@ -78,13 +96,14 @@ contract ASCMinter is ASCBase { require(log.topics.length == 2, "Invalid TokensBurnedForBridging topics"); require(log.topics[0] == BURN_EVENT_SIGNATURE, "Not TokensBurnedForBridging event"); - originTokenAddress = log.address_; + // Log `address` is the source-chain contract that emitted the burn (the emitter). + emitter = log.address_; from = address(uint160(uint256(log.topics[1]))); // data is a single uint256 (32 bytes) require(log.data.length == 32, "Not burn event: data len"); value = abi.decode(log.data, (uint256)); - return (originTokenAddress, from, value); + return (emitter, from, value); } } diff --git a/bridge/contracts/sol/VerifierInterface.sol b/bridge/contracts/sol/VerifierInterface.sol deleted file mode 100644 index 47890470..00000000 --- a/bridge/contracts/sol/VerifierInterface.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -interface INativeQueryVerifier { - struct MerkleProofEntry { - bytes32 hash; - bool isLeft; - } - - struct MerkleProof { - bytes32 root; - MerkleProofEntry[] siblings; - } - - struct ContinuityProof { - bytes32 lowerEndpointDigest; - bytes32[] roots; - } - - function verifyAndEmit( - uint64 chainKey, - uint64 height, - bytes calldata encodedTransaction, - MerkleProof calldata merkleProof, - ContinuityProof calldata continuityProof - ) external returns (bool); - - function calculateTxIndex( - MerkleProof calldata merkle_proof - ) external view returns (uint64); -} - -library NativeQueryVerifierLib { - address constant PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000FD2; - - function getVerifier() internal pure returns (INativeQueryVerifier) { - return INativeQueryVerifier(PRECOMPILE_ADDRESS); - } -} diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index 8b3bf615..44759a46 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -1,7 +1,7 @@ # Custom Contract Bridging > [!NOTE] -> Deploys the **simplified** local `ASCMinter` / `ASCBase` pair โ€” direct `execute` minting, no relayer contracts. See [bridge/README.md](../README.md). +> Deploys the **simplified** `ASCMinter` on shared package `ASCBase` (`@gluwa/usc-contracts` readability) โ€” direct `execute` minting, no relayer contracts. See [bridge/README.md](../README.md). > [!TIP] > This tutorial builds on the previous [Hello Bridge] example -make sure to check it out before @@ -43,7 +43,7 @@ forge create \ --broadcast \ --rpc-url $SOURCE_CHAIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - loan/contracts/sol/TestERC20.sol:TestERC20 + shared/contracts/sol/TestERC20.sol:TestERC20 ``` This should display some output containing the address of your test `ERC20` contract: @@ -81,7 +81,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder ``` Add the library address to your root `.env` so later steps (and the loan tutorial) can reuse it: @@ -96,7 +96,7 @@ Reload: source .env ``` -> **Note:** Deploy from `contracts/decoding/EvmV1Decoder.sol` in the npm package. That library is a shared receipt decoder โ€” not the Outbox/Relayer bridge stack. +> **Note:** Deploy from `contracts/common/EvmV1Decoder.sol` in the npm package. That library is a shared receipt decoder โ€” not the Outbox/Relayer bridge stack. ### 3.2 Deploy `ASCMinter` @@ -105,11 +105,11 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ + --libraries node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` -If deployment fails (nonce, gas, etc.), see [Contributor deploy notes](../contracts/CONTRIBUTING.md). +If deployment fails (nonce, gas, etc.), see [Contributor deploy notes](../contracts/Contributor%20Notes.md). You should get output with the contract address: @@ -133,7 +133,7 @@ Reload: source .env ``` -### 3.4 Deploy wrapped token and register the route +### 3.4 Deploy wrapped token and whitelist the source emitter Deploy the wrapped ERC20 owned by your minter: @@ -152,7 +152,8 @@ Update `.env`: ASC_CUSTOM_MINTABLE_TOKEN= ``` -Register the Sepolia source token โ†’ wrapped token mapping: +Register (whitelist) the Sepolia burn contract as an allowed emitter and map it to the wrapped token. +`ASCMinter` mints **only** when the proved burn logโ€™s emitting address is on this whitelist โ€” a valid proof from any other contract is rejected: ```bash cast send \ @@ -272,7 +273,7 @@ To experiment with customizing ASC logic, edit `bridge/contracts/sol/ASCMinter.s ## Conclusion -Congratulations! You've deployed your own simplified ASC bridge (`ASCMinter` + wrapped token) and completed a trustless mint. +Congratulations! You've deployed your own simplified ASC bridge (`ASCMinter` + wrapped token), whitelisted the Sepolia burn emitter, and completed a trustless mint. Next: [Loan Flow](../../loan/scripts/README.md) reuses your decoder library and extends the pattern with cross-chain loan state. @@ -281,5 +282,5 @@ Next: [Loan Flow](../../loan/scripts/README.md) reuses your decoder library and [step 2]: #2-deploy-a-test-erc20-contract-on-sepolia [step 3.2]: #32-deploy-ascminter [step 5]: #5-submit-a-mint-query-to-the-asc-contract -[Contributor deploy notes]: ../contracts/CONTRIBUTING.md +[Contributor deploy notes]: ../contracts/Contributor%20Notes.md [Bridge Offchain Worker]: ../bridge-offchain-worker/README.md diff --git a/bridge/foundry.toml b/bridge/foundry.toml index 1543fd1e..cac1d449 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -3,7 +3,8 @@ out = "out" src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] -# Resolve @gluwa/usc-contracts from yarn-installed npm package (decoding/EvmV1Decoder). +# Resolve @gluwa/usc-contracts from yarn (readability ASCBase + common/EvmV1Decoder). +# Package source is @gluwa/asc-contracts (file:../usc-contracts or npm); remapped under this alias. allow_paths = ["../node_modules", "../lib"] remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", @@ -16,3 +17,8 @@ optimizer_runs = 200 via_ir = true evm_version = "shanghai" verbosity = 2 + +# CI Foundry enables lint-on-build by default; remapped ../node_modules imports +# currently crash the lint engine after a successful compile. +[lint] +lint_on_build = false diff --git a/bridge/hello-bridge/README.md b/bridge/hello-bridge/README.md index a6027938..0e366eb3 100644 --- a/bridge/hello-bridge/README.md +++ b/bridge/hello-bridge/README.md @@ -1,13 +1,13 @@ # Hello Bridge > [!NOTE] -> This tutorial uses the **simplified ASC bridge** (`ASCMinter` + local `ASCBase`), not the write-ability Outbox/Relayer stack. Overview: [bridge/README.md](../README.md). +> This tutorial uses the **simplified ASC bridge** (`ASCMinter` on package `ASCBase`), not the write-ability Outbox/Relayer stack. Overview: [bridge/README.md](../README.md). This tutorial introduces you to one of the most common uses for a cross chain oracle, **cross chain bridging!** The flow is: 1. **Burn** tokens on the source chain (Sepolia). -2. **Submit proof + mint** on Creditcoin โ€” steps 2 and 3 are combined in `yarn hello_bridge:submit_query` (proof generation, attestation wait, and `ASCMinter.execute`). +2. **Submit proof + mint** on Creditcoin โ€” steps 2 and 3 are combined in `yarn hello_bridge:submit_query` (proof generation, attestation wait, and `ASCMinter.execute`). The pre-deployed ASC already has the Sepolia burner whitelisted as an emitter; only burns from that contract mint. ## Pre-deployed contracts (no deploy step) diff --git a/bridge/test/ASCMinterBurnLog.t.sol b/bridge/test/ASCMinterBurnLog.t.sol index 6f596b7a..f2668d2f 100644 --- a/bridge/test/ASCMinterBurnLog.t.sol +++ b/bridge/test/ASCMinterBurnLog.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import {Test} from "forge-std/Test.sol"; import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; contract ASCMinterBurnLogTest is Test { ASCMinterHarness internal minter; diff --git a/bridge/test/ASCMinterSecurity.t.sol b/bridge/test/ASCMinterSecurity.t.sol new file mode 100644 index 00000000..085c0bcd --- /dev/null +++ b/bridge/test/ASCMinterSecurity.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test} from "forge-std/Test.sol"; +import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; +import {BridgeTestToken} from "../contracts/sol/BridgeTestToken.sol"; + +/// @notice Regression tests for bridge emitter whitelist and query replay dedupe (ASCBase). +contract ASCMinterSecurityTest is Test { + ASCMinterHarness internal minter; + + address internal originEmitter = address(0xBEEF); + address internal spoofedEmitter = address(0xBAD); + + function setUp() public { + minter = new ASCMinterHarness(); + } + + function testEmitterWhitelist_rejectsUnregisteredEmitter() public { + vm.expectRevert("Emitter not whitelisted"); + minter.exposeRequireWhitelistedEmitter(originEmitter); + } + + function testEmitterWhitelist_acceptsAfterWrapOriginToken() public { + BridgeTestToken wrapped = new BridgeTestToken(address(minter)); + minter.wrapOriginToken(originEmitter, address(wrapped)); + + assertTrue(minter.whitelistedEmitters(originEmitter)); + assertEq(minter.wrappedTokens(originEmitter), address(wrapped)); + + minter.exposeRequireWhitelistedEmitter(originEmitter); + } + + function testEmitterWhitelist_rejectsSpoofedEmitterAfterWrap() public { + BridgeTestToken wrapped = new BridgeTestToken(address(minter)); + minter.wrapOriginToken(originEmitter, address(wrapped)); + + vm.expectRevert("Emitter not whitelisted"); + minter.exposeRequireWhitelistedEmitter(spoofedEmitter); + } + + function testQueryDedupe_rejectsProcessedQueryId() public { + bytes32 queryId = keccak256("tutorial-replay-test"); + minter.exposeMarkQueryProcessed(queryId); + + vm.expectRevert("Query already processed"); + minter.exposeRequireFreshQuery(queryId); + } + + function testQueryDedupe_acceptsFreshQueryId() public view { + bytes32 queryId = keccak256("fresh-query"); + minter.exposeRequireFreshQuery(queryId); + } +} diff --git a/bridge/test/harness/ASCMinterHarness.sol b/bridge/test/harness/ASCMinterHarness.sol index eb44115b..b14b439c 100644 --- a/bridge/test/harness/ASCMinterHarness.sol +++ b/bridge/test/harness/ASCMinterHarness.sol @@ -2,14 +2,26 @@ pragma solidity ^0.8.23; import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; contract ASCMinterHarness is ASCMinter { function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) external pure - returns (address originTokenAddress, address from, uint256 value) + returns (address emitter, address from, uint256 value) { return _processBurnLogs(burnLogs); } + + function exposeRequireWhitelistedEmitter(address emitter) external view { + require(whitelistedEmitters[emitter], "Emitter not whitelisted"); + } + + function exposeRequireFreshQuery(bytes32 queryId) external view { + require(!processedQueries[queryId], "Query already processed"); + } + + function exposeMarkQueryProcessed(bytes32 queryId) external { + processedQueries[queryId] = true; + } } diff --git a/foundry.toml b/foundry.toml index 9942e91e..22d97e9c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,6 @@ libs = ["node_modules"] remappings = [ "@openzeppelin/=node_modules/@openzeppelin/", "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/", - "@example/bridge/=bridge/contracts/sol/", ] solc_version = "0.8.30" optimizer = true diff --git a/loan/contracts/abi-creator.sh b/loan/contracts/abi-creator.sh index 268022be..913000e8 100755 --- a/loan/contracts/abi-creator.sh +++ b/loan/contracts/abi-creator.sh @@ -22,10 +22,8 @@ for p in "$sol_directory"/*.sol; do contract_name="${file%.sol}" file_with_extension="$contract_name.json" solc --base-path . --include-path "node_modules" \ - --allow-paths "bridge/contracts/sol" \ "@openzeppelin/=node_modules/@openzeppelin/" \ "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/" \ - "@example/bridge/=bridge/contracts/sol/" \ "$p" \ --combined-json abi --overwrite --json-indent 2 | \ extract_abi "$p" "$contract_name" | jq --indent 2 . > "$abi_directory/$file_with_extension" diff --git a/loan/contracts/abi/ASCLoanManager.json b/loan/contracts/abi/ASCLoanManager.json index 5c06b2e9..f0957848 100644 --- a/loan/contracts/abi/ASCLoanManager.json +++ b/loan/contracts/abi/ASCLoanManager.json @@ -64,11 +64,6 @@ "name": "OwnableUnauthorizedAccount", "type": "error" }, - { - "inputs": [], - "name": "ReentrancyGuardReentrantCall", - "type": "error" - }, { "anonymous": false, "inputs": [ diff --git a/loan/contracts/abi_generation.md b/loan/contracts/abi_generation.md index 02b6563c..fd6da7e2 100644 --- a/loan/contracts/abi_generation.md +++ b/loan/contracts/abi_generation.md @@ -7,4 +7,4 @@ yarn ./loan/contracts/abi-creator.sh ``` -Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts`. ABI regeneration: see [bridge/contracts/CONTRIBUTING.md](../../bridge/contracts/CONTRIBUTING.md). +Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts` (same base as the bridge examples). Shared source-chain `TestERC20` lives under `shared/contracts/`. ABI regeneration: see [bridge/contracts/Contributor Notes.md](../../bridge/contracts/Contributor%20Notes.md) and `./shared/contracts/abi-creator.sh`. diff --git a/loan/contracts/sol/ASCLoanManager.sol b/loan/contracts/sol/ASCLoanManager.sol index 80e5fa7a..27c4989e 100644 --- a/loan/contracts/sol/ASCLoanManager.sol +++ b/loan/contracts/sol/ASCLoanManager.sol @@ -1,22 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; - import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {LoanFlow, LoanStatus, LoanOrder, LoanTerms} from "./LoanTypes.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; -import {ASCBase} from "@example/bridge/ASCBase.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; /** * @title ASCLoanManager * @dev Main contract for managing cross-chain loan orders in the ASC system */ -contract ASCLoanManager is Ownable, ReentrancyGuard, ASCBase { +contract ASCLoanManager is Ownable, ASCBase { using ECDSA for bytes32; enum LoanManagerActions { diff --git a/loan/foundry.toml b/loan/foundry.toml index 3f7c75aa..3263e78a 100644 --- a/loan/foundry.toml +++ b/loan/foundry.toml @@ -3,13 +3,12 @@ out = "out" src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] -# Resolve @gluwa/usc-contracts from yarn-installed npm package (decoding/EvmV1Decoder). -# ASCBase comes from the local bridge example contracts. -allow_paths = ["../node_modules", "../lib", "../bridge"] +# Resolve @gluwa/usc-contracts from yarn (readability ASCBase + common/EvmV1Decoder). +# Same package alias as bridge โ€” one ASCBase for both examples. +allow_paths = ["../node_modules", "../lib"] remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", - "@example/bridge/=../bridge/contracts/sol/", "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" @@ -18,3 +17,8 @@ optimizer_runs = 200 via_ir = true evm_version = "shanghai" verbosity = 2 + +# CI Foundry enables lint-on-build by default; remapped ../node_modules imports +# currently crash the lint engine after a successful compile. +[lint] +lint_on_build = false diff --git a/loan/scripts/README.md b/loan/scripts/README.md index a6cc30f6..963ab450 100644 --- a/loan/scripts/README.md +++ b/loan/scripts/README.md @@ -49,7 +49,7 @@ forge create \ --broadcast \ --rpc-url $SOURCE_CHAIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - loan/contracts/sol/TestERC20.sol:TestERC20 + shared/contracts/sol/TestERC20.sol:TestERC20 ``` This should display some output containing the address of your test `ERC20` contract: @@ -78,7 +78,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder ``` Save the address in `.env`: @@ -94,7 +94,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ + --libraries node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager ``` @@ -501,5 +501,5 @@ If you haven't already, take a look at the [ASC Gitbook] for more information. [Bridge Offchain Worker]: ../../bridge/bridge-offchain-worker/README.md [Hello Bridge]: ../../bridge/hello-bridge/README.md#11-generate-a-new-wallet-address -[ASC Gitbook]: https://docs.attestcoin.org/ +[ASC Gitbook]: https://docs.attestcoin.org/attestcoin-protocol [๐Ÿšฐ testnet faucet]: https://cloud.google.com/application/web3/faucet/ethereum/sepolia diff --git a/loan/scripts/fund_loan.ts b/loan/scripts/fund_loan.ts index 42aceb92..7d18e867 100644 --- a/loan/scripts/fund_loan.ts +++ b/loan/scripts/fund_loan.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; -import ERC20Abi from '../contracts/abi/TestERC20.json'; +import ERC20Abi from '../../shared/contracts/abi/TestERC20.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan/scripts/repay_loan.ts b/loan/scripts/repay_loan.ts index 0f7fedfa..03b98843 100644 --- a/loan/scripts/repay_loan.ts +++ b/loan/scripts/repay_loan.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; -import ERC20Abi from '../contracts/abi/TestERC20.json'; +import ERC20Abi from '../../shared/contracts/abi/TestERC20.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; dotenv.config({ override: true }); diff --git a/loan/test/ASCLoanManagerSourceBinding.t.sol b/loan/test/ASCLoanManagerSourceBinding.t.sol index 7415dca0..a1173388 100644 --- a/loan/test/ASCLoanManagerSourceBinding.t.sol +++ b/loan/test/ASCLoanManagerSourceBinding.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ASCLoanManagerHarness} from "./harness/ASCLoanManagerHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; /// @notice Regression tests for loan source-contract binding โ€” fund/repay logs must come from the registered source contract. contract ASCLoanManagerSourceBindingTest is Test { diff --git a/loan/test/harness/ASCLoanManagerHarness.sol b/loan/test/harness/ASCLoanManagerHarness.sol index db2c8bc4..8afbc274 100644 --- a/loan/test/harness/ASCLoanManagerHarness.sol +++ b/loan/test/harness/ASCLoanManagerHarness.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {ASCLoanManager} from "../../contracts/sol/ASCLoanManager.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/decoding/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; /// @dev Test harness exposing internal log validators for source-contract binding tests. contract ASCLoanManagerHarness is ASCLoanManager { diff --git a/package.json b/package.json index 237cd8b0..0cc8835f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "tsx": "^4.22.4" }, "dependencies": { - "@gluwa/usc-contracts": "0.1.2", + "@gluwa/usc-contracts": "git+https://github.com/gluwa/asc-contracts.git", "@gluwa/usc-sdk": "0.18.0", "@openzeppelin/contracts": "5.4.0", "@types/node": "26.1.0", diff --git a/shared/check_setup.ts b/shared/check_setup.ts index f390a75f..8dad8742 100644 --- a/shared/check_setup.ts +++ b/shared/check_setup.ts @@ -1,13 +1,55 @@ +import fs from 'fs'; +import path from 'path'; import dotenv from 'dotenv'; import { ethers } from 'ethers'; import { chainInfo } from '@gluwa/usc-sdk'; import { isValidContractAddress, isValidPrivateKey } from './utils'; -dotenv.config({ override: true }); - +const REPO_ROOT = path.resolve(__dirname, '..'); const VERIFIER_PRECOMPILE = '0x0000000000000000000000000000000000000FD2'; +type SetupMode = 'hello' | 'hello-bridge' | 'bridge' | 'custom' | 'loan' | 'network'; + +/** Shell/sourced env wins; then tutorial split; then root `.env` fills remaining defaults. */ +function loadTutorialEnv(mode: SetupMode): void { + const snapshot = { ...process.env }; + const files = [path.join(REPO_ROOT, '.env')]; + + if (mode === 'hello' || mode === 'hello-bridge' || mode === 'bridge' || mode === 'custom') { + files.push(path.join(REPO_ROOT, 'bridge', '.env')); + } else if (mode === 'loan') { + files.push(path.join(REPO_ROOT, 'loan', '.env')); + } + + for (const file of files) { + if (fs.existsSync(file)) { + dotenv.config({ path: file, override: true }); + } + } + + for (const [key, value] of Object.entries(snapshot)) { + if (value !== undefined) { + process.env[key] = value; + } + } +} + +function normalizeMode(raw: string | undefined): SetupMode { + const mode = (raw ?? 'hello').toLowerCase(); + switch (mode) { + case 'hello': + case 'hello-bridge': + case 'bridge': + case 'custom': + case 'loan': + case 'network': + return mode; + default: + throw new Error(`Unknown mode "${mode}". Use: hello | bridge | loan | network`); + } +} + type CheckResult = { ok: boolean; message: string }; function pass(message: string): CheckResult { @@ -202,8 +244,94 @@ async function runBridgeChecks(): Promise { return printResults(results); } +async function runLoanChecks(): Promise { + const results: CheckResult[] = []; + + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + results.push( + sourceChainKey > 0 ? pass(`SOURCE_CHAIN_KEY: ${sourceChainKey}`) : fail('SOURCE_CHAIN_KEY: missing or invalid') + ); + + results.push(await checkRpc('CREDITCOIN_RPC_URL', process.env.CREDITCOIN_RPC_URL)); + results.push(await checkRpc('SOURCE_CHAIN_RPC_URL', process.env.SOURCE_CHAIN_RPC_URL)); + results.push(await checkProofBuilder(process.env.PROOF_BUILDER_URL)); + results.push(await checkVerifierPrecompile(process.env.CREDITCOIN_RPC_URL)); + results.push(await checkChainKeyAttestation(process.env.CREDITCOIN_RPC_URL, sourceChainKey)); + + results.push( + isValidPrivateKey(process.env.CREDITCOIN_WALLET_PRIVATE_KEY) + ? pass('CREDITCOIN_WALLET_PRIVATE_KEY: set') + : fail('CREDITCOIN_WALLET_PRIVATE_KEY: missing or invalid') + ); + results.push( + isValidPrivateKey(process.env.LENDER_WALLET_PRIVATE_KEY) + ? pass('LENDER_WALLET_PRIVATE_KEY: set') + : fail('LENDER_WALLET_PRIVATE_KEY: missing or invalid') + ); + results.push( + isValidPrivateKey(process.env.BORROWER_WALLET_PRIVATE_KEY) + ? pass('BORROWER_WALLET_PRIVATE_KEY: set') + : fail('BORROWER_WALLET_PRIVATE_KEY: missing or invalid') + ); + + if (isValidContractAddress(process.env.EVM_V1_DECODER_LIBRARY_ADDRESS)) { + results.push(pass(`EVM_V1_DECODER_LIBRARY_ADDRESS: ${process.env.EVM_V1_DECODER_LIBRARY_ADDRESS}`)); + } else { + results.push( + pass('EVM_V1_DECODER_LIBRARY_ADDRESS: not set (reuse from bridge tutorial or deploy once on Creditcoin)') + ); + } + + if (isValidContractAddress(process.env.ASC_LOAN_MANAGER_CONTRACT_ADDRESS)) { + results.push( + await checkContractCode( + 'ASC_LOAN_MANAGER_CONTRACT_ADDRESS', + process.env.CREDITCOIN_RPC_URL, + process.env.ASC_LOAN_MANAGER_CONTRACT_ADDRESS + ) + ); + } else { + results.push(fail('ASC_LOAN_MANAGER_CONTRACT_ADDRESS: not configured')); + } + + if (isValidContractAddress(process.env.SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS)) { + results.push( + await checkContractCode( + 'SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS', + process.env.SOURCE_CHAIN_RPC_URL, + process.env.SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS + ) + ); + } else { + results.push(fail('SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS: not configured')); + } + + if (isValidContractAddress(process.env.SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS)) { + results.push( + await checkContractCode( + 'SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS', + process.env.SOURCE_CHAIN_RPC_URL, + process.env.SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS + ) + ); + } else { + results.push(fail('SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS: not configured')); + } + + return printResults(results); +} + async function main(): Promise { - const mode = (process.argv[2] ?? 'hello').toLowerCase(); + let mode: SetupMode; + try { + mode = normalizeMode(process.argv[2]); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(msg); + process.exit(1); + } + + loadTutorialEnv(mode); console.log(`Tutorial setup check (${mode})\n`); @@ -217,16 +345,16 @@ async function main(): Promise { case 'custom': ok = await runBridgeChecks(); break; + case 'loan': + ok = await runLoanChecks(); + break; case 'network': ok = await runNetworkChecks(); break; - default: - console.error(`Unknown mode "${mode}". Use: hello | bridge | network`); - process.exit(1); } if (!ok) { - console.error('\nFix the items above, then re-run: yarn utils:check_setup'); + console.error(`\nFix the items above, then re-run: yarn utils:check_setup ${mode}`); process.exit(1); } diff --git a/shared/contracts/abi-creator.sh b/shared/contracts/abi-creator.sh new file mode 100644 index 00000000..32eaee5c --- /dev/null +++ b/shared/contracts/abi-creator.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# WARNING: execute this from the repository root. +set -euo pipefail + +sol_directory="shared/contracts/sol" +abi_directory="shared/contracts/abi" + +extract_abi() { + local path="$1" + local name="$2" + jq -c --arg path "$path" --arg name "$name" ' + (.contracts // {}) as $c + | ($c[$path + ":" + $name].abi + // (($c | to_entries | map(select(.key | endswith(":" + $name))) | .[0].value.abi) // null) + // {}) + ' +} + +mkdir -p "$abi_directory" + +for p in "$sol_directory"/*.sol; do + file=$(basename "$p") + contract_name="${file%.sol}" + file_with_extension="$contract_name.json" + solc --base-path . --include-path "node_modules" \ + "@openzeppelin/=node_modules/@openzeppelin/" \ + "$p" \ + --combined-json abi --overwrite --json-indent 2 | \ + extract_abi "$p" "$contract_name" | jq --indent 2 . > "$abi_directory/$file_with_extension" +done diff --git a/loan/contracts/abi/TestERC20.json b/shared/contracts/abi/TestERC20.json similarity index 100% rename from loan/contracts/abi/TestERC20.json rename to shared/contracts/abi/TestERC20.json diff --git a/loan/contracts/sol/TestERC20.sol b/shared/contracts/sol/TestERC20.sol similarity index 87% rename from loan/contracts/sol/TestERC20.sol rename to shared/contracts/sol/TestERC20.sol index 3ce1b133..b8c1c791 100644 --- a/loan/contracts/sol/TestERC20.sol +++ b/shared/contracts/sol/TestERC20.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +/// @notice Minimal Sepolia burner ERC20 shared by bridge and loan tutorials. contract TestERC20 is ERC20 { address public constant BURN_ADDRESS = address(1); // 0x...01 @@ -12,7 +13,6 @@ contract TestERC20 is ERC20 { event TokensBurnedForBridging(address indexed from, uint256 value); constructor() ERC20("Burn Test", "TEST") { - // Mint sender initial supply _mint(msg.sender, 1_000_000 ether); } @@ -24,8 +24,7 @@ contract TestERC20 is ERC20 { return true; } - /// @notice Mint new tokens to a specified address. - /// @param amount The amount of tokens to mint + /// @notice Mint new tokens to the caller. function mint(uint256 amount) external returns (bool) { _mint(msg.sender, amount); return true; diff --git a/shared/utils/index.ts b/shared/utils/index.ts index e0007a1f..9968c3ae 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts @@ -143,7 +143,7 @@ export async function computeGasLimitForMinter( proofData: proofProvider.ContinuityResponse, signerAddress: string ): Promise { - const action = 0; // Mint action (see MinterActions in ASCMinter) + const action = 1; // Mint action (see MinterActions in ASCMinter: None=0, Mint=1) const chainKey = proofData.chainKey; const height = proofData.headerNumber; const encodedTransaction = proofData.txBytes; @@ -250,7 +250,7 @@ export async function submitProofToMinter( proofData: proofProvider.ContinuityResponse, gasLimit: bigint ): Promise { - const action = 0; // Mint action (see MinterActions in ASCMinter) + const action = 1; // Mint action (see MinterActions in ASCMinter: None=0, Mint=1) const chainKey = proofData.chainKey; const height = proofData.headerNumber; const encodedTransaction = proofData.txBytes; diff --git a/yarn.lock b/yarn.lock index a17daf3b..d46405bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,8 +7,135 @@ resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz" integrity sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ== -"@esbuild/win32-x64@0.28.0": - version "0.28.0" +"@esbuild/aix-ppc64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz#bf6e10303bcf2e7c686975fa52f937ec2728d8bc" + integrity sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ== + +"@esbuild/android-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz#0c6246bc8d2c4d172aac2db3fb1190d72bd65504" + integrity sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A== + +"@esbuild/android-arm@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.28.2.tgz#2d84ece6a4e2684d92be26ee13d42757d831c381" + integrity sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg== + +"@esbuild/android-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.28.2.tgz#fc38d4d6358d8dc1cf53f09f7589fe436eb64801" + integrity sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q== + +"@esbuild/darwin-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz#f83afeeac1d7dac01c7a2fd012b3e451a0591fcc" + integrity sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw== + +"@esbuild/darwin-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz#510147c055a795588dbbe14fd6b1b8ad0a2f30de" + integrity sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw== + +"@esbuild/freebsd-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz#093b9200ecf0b115ba4e5e248a7485c9c5f8bd5e" + integrity sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw== + +"@esbuild/freebsd-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz#0be22b6df925d213e841ea87123af5df80b0faf7" + integrity sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg== + +"@esbuild/linux-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz#1bdbc651cda9ba9995c53ed9c71ceaa65094762d" + integrity sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug== + +"@esbuild/linux-arm@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz#beb12ad72b84f72d28488cc1b8ee9f7eb141d753" + integrity sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w== + +"@esbuild/linux-ia32@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz#b81f9d55529b45c206a46a138214b1aa6879696b" + integrity sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ== + +"@esbuild/linux-loong64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz#598667241a04c99b76ed6ef940ac50038c419f98" + integrity sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ== + +"@esbuild/linux-mips64el@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz#1c51eb9cea903f53d97b5af3b1841db70f5596ca" + integrity sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA== + +"@esbuild/linux-ppc64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz#63dd61f17ceb31a81227f413feac8a71bc2c51f2" + integrity sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ== + +"@esbuild/linux-riscv64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz#3763b08fde5cf25ab1facb8e7752edfe45fbfc27" + integrity sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA== + +"@esbuild/linux-s390x@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz#1a137ff293a82906eb3176385bd7e8e0e5cfb7cb" + integrity sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg== + +"@esbuild/linux-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz#268b36211c146ca54f8fe12c578a8d6ef8979485" + integrity sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ== + +"@esbuild/netbsd-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz#22571ad951d62bb6accc82d8d1fad5c8c1ac0ba1" + integrity sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw== + +"@esbuild/netbsd-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz#42fcc57297eb0a0ca3f5fc475291f4c1a3f7c0de" + integrity sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw== + +"@esbuild/openbsd-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz#9eb32af104ac3dacf4edca01f596664aab0c73ef" + integrity sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ== + +"@esbuild/openbsd-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz#febed2402d6088225e91f20fb4ce2522ad0a4efd" + integrity sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw== + +"@esbuild/openharmony-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz#85641c3d466428bfbccea5f21c26836663fef5ce" + integrity sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q== + +"@esbuild/sunos-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz#a736f9d8962481045fc4c3e54f5479f22c870fb4" + integrity sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g== + +"@esbuild/win32-arm64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz#ee5ab40fad186201b652a33f8a5eb149e9e42532" + integrity sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ== + +"@esbuild/win32-ia32@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz#c40d28a6d99a127da6711f2afd74b11cb63b06a7" + integrity sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA== + +"@esbuild/win32-x64@0.28.2": + version "0.28.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz#b21affb804cc167c133d95f45b3a1dc1323b9a87" + integrity sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" @@ -42,10 +169,11 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@gluwa/usc-contracts@0.1.2": - version "0.1.2" - resolved "https://registry.npmjs.org/@gluwa/usc-contracts/-/usc-contracts-0.1.2.tgz" - integrity sha512-6GbBApjqhb6uWGzo6mLOZxH2RNq8iCOoRuYXn97gIQIaIHMk/pjQdDk2vJolX11TnhQbcrtoE/em/RDQyrJJdQ== +"@gluwa/usc-contracts@git+https://github.com/gluwa/asc-contracts.git": + version "0.2.0" + resolved "git+https://github.com/gluwa/asc-contracts.git#7a81d6824f1b5f2abe0f5211e92911bdd9357bc6" + dependencies: + "@openzeppelin/contracts" "5.1.0" "@gluwa/usc-sdk@0.18.0": version "0.18.0" @@ -109,6 +237,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.1.0.tgz#4e61162f2a2bf414c4e10c45eca98ce5f1aadbd4" + integrity sha512-p1ULhl7BXzjjbha5aqst+QMLY+4/LCWADXOCsmLHRM77AqiPjnd9vvUN9sosUfhL9JGKpZ0TjEGxgvnizmWGSA== + "@openzeppelin/contracts@5.4.0": version "5.4.0" resolved "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz" @@ -142,7 +275,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.5.0" -"@typescript-eslint/parser@^8.62.1", "@typescript-eslint/parser@8.62.1": +"@typescript-eslint/parser@8.62.1": version "8.62.1" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz" integrity sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA== @@ -170,7 +303,7 @@ "@typescript-eslint/types" "8.62.1" "@typescript-eslint/visitor-keys" "8.62.1" -"@typescript-eslint/tsconfig-utils@^8.62.1", "@typescript-eslint/tsconfig-utils@8.62.1": +"@typescript-eslint/tsconfig-utils@8.62.1", "@typescript-eslint/tsconfig-utils@^8.62.1": version "8.62.1" resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz" integrity sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g== @@ -186,7 +319,7 @@ debug "^4.4.3" ts-api-utils "^2.5.0" -"@typescript-eslint/types@^8.62.1", "@typescript-eslint/types@8.62.1": +"@typescript-eslint/types@8.62.1", "@typescript-eslint/types@^8.62.1": version "8.62.1" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz" integrity sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q== @@ -234,7 +367,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: +acorn@^8.9.0: version "8.15.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -388,7 +521,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dotenv@^17.2.3, dotenv@17.4.2: +dotenv@17.4.2, dotenv@^17.2.3: version "17.4.2" resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz" integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw== @@ -430,34 +563,36 @@ es-set-tostringtag@^2.1.0: hasown "^2.0.2" esbuild@~0.28.0: - version "0.28.0" + version "0.28.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.2.tgz#0f43bd1bad955b72d24e2261e3abe5957ccf0816" + integrity sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA== optionalDependencies: - "@esbuild/aix-ppc64" "0.28.0" - "@esbuild/android-arm" "0.28.0" - "@esbuild/android-arm64" "0.28.0" - "@esbuild/android-x64" "0.28.0" - "@esbuild/darwin-arm64" "0.28.0" - "@esbuild/darwin-x64" "0.28.0" - "@esbuild/freebsd-arm64" "0.28.0" - "@esbuild/freebsd-x64" "0.28.0" - "@esbuild/linux-arm" "0.28.0" - "@esbuild/linux-arm64" "0.28.0" - "@esbuild/linux-ia32" "0.28.0" - "@esbuild/linux-loong64" "0.28.0" - "@esbuild/linux-mips64el" "0.28.0" - "@esbuild/linux-ppc64" "0.28.0" - "@esbuild/linux-riscv64" "0.28.0" - "@esbuild/linux-s390x" "0.28.0" - "@esbuild/linux-x64" "0.28.0" - "@esbuild/netbsd-arm64" "0.28.0" - "@esbuild/netbsd-x64" "0.28.0" - "@esbuild/openbsd-arm64" "0.28.0" - "@esbuild/openbsd-x64" "0.28.0" - "@esbuild/openharmony-arm64" "0.28.0" - "@esbuild/sunos-x64" "0.28.0" - "@esbuild/win32-arm64" "0.28.0" - "@esbuild/win32-ia32" "0.28.0" - "@esbuild/win32-x64" "0.28.0" + "@esbuild/aix-ppc64" "0.28.2" + "@esbuild/android-arm" "0.28.2" + "@esbuild/android-arm64" "0.28.2" + "@esbuild/android-x64" "0.28.2" + "@esbuild/darwin-arm64" "0.28.2" + "@esbuild/darwin-x64" "0.28.2" + "@esbuild/freebsd-arm64" "0.28.2" + "@esbuild/freebsd-x64" "0.28.2" + "@esbuild/linux-arm" "0.28.2" + "@esbuild/linux-arm64" "0.28.2" + "@esbuild/linux-ia32" "0.28.2" + "@esbuild/linux-loong64" "0.28.2" + "@esbuild/linux-mips64el" "0.28.2" + "@esbuild/linux-ppc64" "0.28.2" + "@esbuild/linux-riscv64" "0.28.2" + "@esbuild/linux-s390x" "0.28.2" + "@esbuild/linux-x64" "0.28.2" + "@esbuild/netbsd-arm64" "0.28.2" + "@esbuild/netbsd-x64" "0.28.2" + "@esbuild/openbsd-arm64" "0.28.2" + "@esbuild/openbsd-x64" "0.28.2" + "@esbuild/openharmony-arm64" "0.28.2" + "@esbuild/sunos-x64" "0.28.2" + "@esbuild/win32-arm64" "0.28.2" + "@esbuild/win32-ia32" "0.28.2" + "@esbuild/win32-x64" "0.28.2" escape-string-regexp@^4.0.0: version "4.0.0" @@ -487,7 +622,7 @@ eslint-visitor-keys@^5.0.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.57.0, "eslint@^8.57.0 || ^9.0.0 || ^10.0.0", eslint@>=7.0.0: +eslint@^8.57.0: version "8.57.1" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -644,19 +779,26 @@ follow-redirects@^1.16.0: integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== form-data@^4.0.5: - version "4.0.5" + version "4.0.6" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827" + integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ== 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" + hasown "^2.0.4" + mime-types "^2.1.35" fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -746,6 +888,13 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hasown@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + ignore@^5.2.0: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" @@ -805,7 +954,9 @@ isexe@^2.0.0: integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== js-yaml@^4.1.0: - version "4.1.1" + version "4.3.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb" + integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA== dependencies: argparse "^2.0.1" @@ -861,8 +1012,10 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.35: version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" @@ -945,7 +1098,7 @@ path-key@^3.1.0: resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -"picomatch@^3 || ^4", picomatch@^4.0.4: +picomatch@^4.0.4: version "4.0.7" resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz" integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== @@ -1079,7 +1232,7 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@>=4.8.4, "typescript@>=4.8.4 <6.1.0", typescript@6.0.2: +typescript@6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz" integrity sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ== From 3fcd47103ebd3734bf9c9c2db280dc431f6ff26a Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:14:50 -0600 Subject: [PATCH 08/31] Use PAT to access private repo for CI --- .github/workflows/bridge-offchain-worker.yml | 11 ++++++++++ .../workflows/custom-contracts-bridging.yml | 11 ++++++++++ .github/workflows/hello-bridge.yml | 11 ++++++++++ .github/workflows/javascript-checks.yaml | 11 ++++++++++ .github/workflows/loan-flow.yml | 11 ++++++++++ .github/workflows/solidity.yml | 22 +++++++++++++++++++ README.md | 16 ++++++++++++-- package.json | 2 +- yarn.lock | 9 ++++++-- 9 files changed, 99 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index 43c4475d..06178b2b 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -163,6 +163,17 @@ jobs: cache: 'yarn' - run: npm install -g yarn + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Setup id: setup run: | diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index 759c240c..5c8f837a 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -163,6 +163,17 @@ jobs: cache: 'yarn' - run: npm install -g yarn + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Setup id: setup run: | diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index 8ca8f255..bcccb0ae 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -163,6 +163,17 @@ jobs: cache: 'yarn' - run: npm install -g yarn + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Setup id: setup run: | diff --git a/.github/workflows/javascript-checks.yaml b/.github/workflows/javascript-checks.yaml index 6fc53533..42ec7314 100644 --- a/.github/workflows/javascript-checks.yaml +++ b/.github/workflows/javascript-checks.yaml @@ -43,6 +43,17 @@ jobs: - name: Install forge-std run: git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Install dependencies run: | if [ -f yarn.lock ]; then diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 404e2f3c..fff56e71 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -163,6 +163,17 @@ jobs: cache: 'yarn' - run: npm install -g yarn + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Setup id: setup run: | diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index 48262a05..c1e2088c 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -33,6 +33,17 @@ jobs: cache: 'yarn' - run: npm install -g yarn + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Compile and test smart contracts run: | yarn install --ignore-engines @@ -66,6 +77,17 @@ jobs: cache: 'yarn' - run: npm install -g yarn + - name: Checkout private ASC contracts (file:../usc-contracts) + env: + ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + git clone --depth 1 \ + "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ + "${GITHUB_WORKSPACE}/../usc-contracts" - name: Regenerate the ABI run: | yarn install --ignore-engines diff --git a/README.md b/README.md index 371874da..78abad9f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCMinter` on shared package `ASCBase`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). -`@gluwa/usc-contracts` is resolved from [`asc-contracts`](https://github.com/gluwa/asc-contracts) (`git+https`), which provides one shared readability `ASCBase` for bridge and loan. +`@gluwa/usc-contracts` resolves via `file:../usc-contracts` (sibling checkout of private [`asc-contracts`](https://github.com/gluwa/asc-contracts)). Locally clone that repo next to this one. CI uses secret `ASC_CONTRACTS_READ_TOKEN` (see [Option B setup](#ci-private-asc-contracts-option-b)). Learn how to use the Creditcoin Decentralized Oracle through guided tutorials where you set up and interact with your own decentralized bridge and loan examples. @@ -32,9 +32,12 @@ Before attempting any of the tutorials, make sure the following are installed: - [yarn] - [foundry] -Install dependencies from the repository root: +Install dependencies from the repository root (requires sibling `../usc-contracts`): ```sh +# from parent of this repo +git clone https://github.com/gluwa/asc-contracts.git usc-contracts +cd attestcoin-protocol-examples # or usc-testnet-bridge-examples yarn ``` @@ -67,6 +70,15 @@ Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ Full verification checklist: [VERIFICATION.md](./VERIFICATION.md). +### CI: private `asc-contracts` (Option B) + +GitHub Actions cannot clone `gluwa/asc-contracts` anonymously. Add a repo/org secret: + +1. Create a fine-grained PAT (or GitHub App token) with **Contents: Read** on `gluwa/asc-contracts`. +2. Add secret **`ASC_CONTRACTS_READ_TOKEN`** on this repository (Settings โ†’ Secrets and variables โ†’ Actions). +3. Workflows check out `asc-contracts` to `../usc-contracts`, then `yarn` resolves `"@gluwa/usc-contracts": "file:../usc-contracts"`. + +Fork PRs will not receive this secret unless you use a trusted workflow pattern. ## Tutorials We recommend going through them in this order: diff --git a/package.json b/package.json index 0cc8835f..a6967c52 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "tsx": "^4.22.4" }, "dependencies": { - "@gluwa/usc-contracts": "git+https://github.com/gluwa/asc-contracts.git", + "@gluwa/usc-contracts": "file:../usc-contracts", "@gluwa/usc-sdk": "0.18.0", "@openzeppelin/contracts": "5.4.0", "@types/node": "26.1.0", diff --git a/yarn.lock b/yarn.lock index d46405bd..6e01e0f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -169,11 +169,11 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@gluwa/usc-contracts@git+https://github.com/gluwa/asc-contracts.git": +"@gluwa/usc-contracts@file:../usc-contracts": version "0.2.0" - resolved "git+https://github.com/gluwa/asc-contracts.git#7a81d6824f1b5f2abe0f5211e92911bdd9357bc6" dependencies: "@openzeppelin/contracts" "5.1.0" + "@openzeppelin/contracts-upgradeable" "5.1.0" "@gluwa/usc-sdk@0.18.0": version "0.18.0" @@ -237,6 +237,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts-upgradeable@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.1.0.tgz#4d37648b7402929c53e2ff6e45749ecff91eb2b6" + integrity sha512-AIElwP5Ck+cslNE+Hkemf5SxjJoF4wBvvjxc27Rp+9jaPs/CLIaUBMYe1FNzhdiN0cYuwGRmYaRHmmntuiju4Q== + "@openzeppelin/contracts@5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.1.0.tgz#4e61162f2a2bf414c4e10c45eca98ce5f1aadbd4" From b913dc20b0f9313287b7ae8c89d8a7df538206e9 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:23:53 -0600 Subject: [PATCH 09/31] Fix CI by installing asc-contracts from GitHub main with read PAT --- .github/actions/install-js-deps/action.yml | 38 + .github/workflows/bridge-offchain-worker.yml | 18 +- .../workflows/custom-contracts-bridging.yml | 18 +- .github/workflows/examples.yml | 4 + .github/workflows/hello-bridge.yml | 18 +- .github/workflows/javascript-checks.yaml | 22 +- .github/workflows/loan-flow.yml | 18 +- .github/workflows/solidity.yml | 36 +- README.md | 15 +- bridge/foundry.toml | 2 +- package-lock.json | 2139 ----------------- package.json | 2 +- yarn.lock | 9 +- 13 files changed, 87 insertions(+), 2252 deletions(-) create mode 100644 .github/actions/install-js-deps/action.yml delete mode 100644 package-lock.json diff --git a/.github/actions/install-js-deps/action.yml b/.github/actions/install-js-deps/action.yml new file mode 100644 index 00000000..948f67f6 --- /dev/null +++ b/.github/actions/install-js-deps/action.yml @@ -0,0 +1,38 @@ +name: Install JS deps +description: yarn install with authenticated access to private gluwa/asc-contracts on main +inputs: + token: + description: GitHub token with Contents:Read on gluwa/asc-contracts (ASC_CONTRACTS_READ_TOKEN) + required: true + upgrade_asc_contracts: + description: Re-resolve git+...#main to the current tip (recommended for CI) + required: false + default: "true" +runs: + using: composite + steps: + - name: Authenticate git for gluwa/asc-contracts + shell: bash + env: + ASC_CONTRACTS_READ_TOKEN: ${{ inputs.token }} + run: | + if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + exit 1 + fi + # So yarn can resolve git+https://github.com/gluwa/asc-contracts.git#main + git config --global url."https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/".insteadOf "https://github.com/" + + - name: yarn install + shell: bash + run: yarn install --ignore-engines + + - name: Upgrade @gluwa/usc-contracts to latest main + if: inputs.upgrade_asc_contracts == 'true' + shell: bash + run: | + yarn upgrade @gluwa/usc-contracts --ignore-engines + echo "Resolved @gluwa/usc-contracts:" + yarn list --pattern '@gluwa/usc-contracts' --depth=0 || true + ls -la node_modules/@gluwa/usc-contracts/contracts/readability || true + ls -la node_modules/@gluwa/usc-contracts/contracts/common || true diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index 06178b2b..dcd2b65f 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -163,22 +163,14 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Setup id: setup run: | - yarn install --ignore-engines - SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" echo "INFO: generate a new wallet" diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index 5c8f837a..4ab8c43f 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -163,22 +163,14 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Setup id: setup run: | - yarn install --ignore-engines - SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" echo "INFO: generate a new wallet" diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 8ae1f496..bf9e45d9 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -25,6 +25,7 @@ jobs: with: source_chain_kind: "anvil" asc_kind: "local" + secrets: inherit custom-contracts-bridging: needs: build @@ -32,6 +33,7 @@ jobs: with: source_chain_kind: "anvil" asc_kind: "local" + secrets: inherit bridge-offchain-worker: needs: build @@ -39,6 +41,7 @@ jobs: with: source_chain_kind: "anvil" asc_kind: "local" + secrets: inherit loan-flow: needs: build @@ -46,3 +49,4 @@ jobs: with: source_chain_kind: "anvil" asc_kind: "local" + secrets: inherit diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index bcccb0ae..23edb824 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -163,22 +163,14 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Setup id: setup run: | - yarn install --ignore-engines - SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" echo "INFO: generate a new wallet" diff --git a/.github/workflows/javascript-checks.yaml b/.github/workflows/javascript-checks.yaml index 42ec7314..fd19e2aa 100644 --- a/.github/workflows/javascript-checks.yaml +++ b/.github/workflows/javascript-checks.yaml @@ -43,24 +43,10 @@ jobs: - name: Install forge-std run: git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" - - name: Install dependencies - run: | - if [ -f yarn.lock ]; then - yarn install --ignore-engines - else - npm clean-install - fi + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - name: Execute command ${{ matrix.command }} run: | diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index fff56e71..efcfbf19 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -163,22 +163,14 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Setup id: setup run: | - yarn install --ignore-engines - SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" echo "INFO: generate a new wallet" diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index c1e2088c..e680c576 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -33,21 +33,13 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Compile and test smart contracts run: | - yarn install --ignore-engines - echo "INFO: forge remappings (bridge)" ~/.foundry/bin/forge remappings --root bridge || true echo "INFO: forge remappings (loan)" @@ -77,21 +69,13 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Checkout private ASC contracts (file:../usc-contracts) - env: - ASC_CONTRACTS_READ_TOKEN: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" - exit 1 - fi - git clone --depth 1 \ - "https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/gluwa/asc-contracts.git" \ - "${GITHUB_WORKSPACE}/../usc-contracts" + - name: Install dependencies (asc-contracts from GitHub main) + uses: ./.github/actions/install-js-deps + with: + token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Regenerate the ABI run: | - yarn install --ignore-engines - solc --version chmod +x ./bridge/contracts/abi-creator.sh ./loan/contracts/abi-creator.sh ./shared/contracts/abi-creator.sh ./bridge/contracts/abi-creator.sh diff --git a/README.md b/README.md index 78abad9f..df68b0fa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCMinter` on shared package `ASCBase`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). -`@gluwa/usc-contracts` resolves via `file:../usc-contracts` (sibling checkout of private [`asc-contracts`](https://github.com/gluwa/asc-contracts)). Locally clone that repo next to this one. CI uses secret `ASC_CONTRACTS_READ_TOKEN` (see [Option B setup](#ci-private-asc-contracts-option-b)). +`@gluwa/usc-contracts` is installed from GitHub [`gluwa/asc-contracts`](https://github.com/gluwa/asc-contracts) `main` (`git+https://github.com/gluwa/asc-contracts.git#main`). CI authenticates with secret `ASC_CONTRACTS_READ_TOKEN` (see [CI setup](#ci-private-asc-contracts)). Learn how to use the Creditcoin Decentralized Oracle through guided tutorials where you set up and interact with your own decentralized bridge and loan examples. @@ -32,12 +32,11 @@ Before attempting any of the tutorials, make sure the following are installed: - [yarn] - [foundry] -Install dependencies from the repository root (requires sibling `../usc-contracts`): +Install dependencies from the repository root (needs read access to private `gluwa/asc-contracts`): ```sh -# from parent of this repo -git clone https://github.com/gluwa/asc-contracts.git usc-contracts -cd attestcoin-protocol-examples # or usc-testnet-bridge-examples +# Optional if HTTPS clone of gluwa/asc-contracts fails without auth: +# git config --global url."https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/".insteadOf "https://github.com/" yarn ``` @@ -70,13 +69,13 @@ Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ Full verification checklist: [VERIFICATION.md](./VERIFICATION.md). -### CI: private `asc-contracts` (Option B) +### CI: private `asc-contracts` -GitHub Actions cannot clone `gluwa/asc-contracts` anonymously. Add a repo/org secret: +GitHub Actions cannot fetch `gluwa/asc-contracts` anonymously. Add a repo/org secret: 1. Create a fine-grained PAT (or GitHub App token) with **Contents: Read** on `gluwa/asc-contracts`. 2. Add secret **`ASC_CONTRACTS_READ_TOKEN`** on this repository (Settings โ†’ Secrets and variables โ†’ Actions). -3. Workflows check out `asc-contracts` to `../usc-contracts`, then `yarn` resolves `"@gluwa/usc-contracts": "file:../usc-contracts"`. +3. Workflows run `.github/actions/install-js-deps`, which rewrites `https://github.com/` git URLs with that token, runs `yarn install`, then `yarn upgrade @gluwa/usc-contracts` so CI always uses the current `main` tip (`git+https://github.com/gluwa/asc-contracts.git#main`). Fork PRs will not receive this secret unless you use a trusted workflow pattern. ## Tutorials diff --git a/bridge/foundry.toml b/bridge/foundry.toml index cac1d449..e3f80ffc 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -4,7 +4,7 @@ src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] # Resolve @gluwa/usc-contracts from yarn (readability ASCBase + common/EvmV1Decoder). -# Package source is @gluwa/asc-contracts (file:../usc-contracts or npm); remapped under this alias. +# Package source is gluwa/asc-contracts (git #main via yarn); remapped under this alias. allow_paths = ["../node_modules", "../lib"] remappings = [ "@openzeppelin/=../node_modules/@openzeppelin/", diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 12303dcc..00000000 --- a/package-lock.json +++ /dev/null @@ -1,2139 +0,0 @@ -{ - "name": "asc-testnet-bridge-examples", - "version": "0.2.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "asc-testnet-bridge-examples", - "version": "0.2.0", - "license": "MIT", - "dependencies": { - "@gluwa/usc-contracts": "0.1.2", - "@gluwa/usc-sdk": "0.18.0", - "@openzeppelin/contracts": "5.4.0", - "@types/node": "26.1.0", - "dotenv": "17.4.2", - "ethers": "^6.17.0" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "8.62.1", - "@typescript-eslint/parser": "8.62.1", - "eslint": "^8.57.0", - "eslint-config-prettier": "^10.1.8", - "prettier": "^3.9.4", - "tsx": "^4.22.4", - "typescript": "6.0.2" - } - }, - "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==" - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@gluwa/usc-contracts": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@gluwa/usc-contracts/-/usc-contracts-0.1.2.tgz", - "integrity": "sha512-6GbBApjqhb6uWGzo6mLOZxH2RNq8iCOoRuYXn97gIQIaIHMk/pjQdDk2vJolX11TnhQbcrtoE/em/RDQyrJJdQ==" - }, - "node_modules/@gluwa/usc-sdk": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@gluwa/usc-sdk/-/usc-sdk-0.18.0.tgz", - "integrity": "sha512-iUEXPAp1gB/HDYkXad+StVJvs0Yz2ZIbzijc8SaBflGJ95hPnVahyY+tsP2PApswSQaX2uIBM7Cb0jpZ+KBzTQ==", - "dependencies": { - "axios": "^1.13.2", - "dotenv": "^17.2.3", - "ethers": "^6.15.0", - "exponential-backoff": "^3.1.3" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "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/@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/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@openzeppelin/contracts": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", - "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", - "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", - "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/type-utils": "8.62.1", - "@typescript-eslint/utils": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.62.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", - "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", - "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", - "dev": true, - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.1", - "@typescript-eslint/types": "^8.62.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", - "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", - "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", - "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/utils": "8.62.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", - "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", - "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", - "dev": true, - "dependencies": { - "@typescript-eslint/project-service": "8.62.1", - "@typescript-eslint/tsconfig-utils": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", - "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", - "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.62.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "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/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.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/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "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/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "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/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "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/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/esbuild": { - "version": "0.28.0", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ethers": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.17.0.tgz", - "integrity": "sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@adraffy/ens-normalize": "1.11.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.21.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/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/ethers/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/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "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, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "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/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "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/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-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/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "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/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", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "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==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "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/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "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": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "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/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "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/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index a6967c52..4477f9ff 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "tsx": "^4.22.4" }, "dependencies": { - "@gluwa/usc-contracts": "file:../usc-contracts", + "@gluwa/usc-contracts": "git+https://github.com/gluwa/asc-contracts.git#main", "@gluwa/usc-sdk": "0.18.0", "@openzeppelin/contracts": "5.4.0", "@types/node": "26.1.0", diff --git a/yarn.lock b/yarn.lock index 6e01e0f3..640f082a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -169,11 +169,11 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@gluwa/usc-contracts@file:../usc-contracts": +"@gluwa/usc-contracts@git+https://github.com/gluwa/asc-contracts.git#main": version "0.2.0" + resolved "git+https://github.com/gluwa/asc-contracts.git#6d49d0833a87d09fdcc56cb932de672b90c29c12" dependencies: "@openzeppelin/contracts" "5.1.0" - "@openzeppelin/contracts-upgradeable" "5.1.0" "@gluwa/usc-sdk@0.18.0": version "0.18.0" @@ -237,11 +237,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openzeppelin/contracts-upgradeable@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.1.0.tgz#4d37648b7402929c53e2ff6e45749ecff91eb2b6" - integrity sha512-AIElwP5Ck+cslNE+Hkemf5SxjJoF4wBvvjxc27Rp+9jaPs/CLIaUBMYe1FNzhdiN0cYuwGRmYaRHmmntuiju4Q== - "@openzeppelin/contracts@5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.1.0.tgz#4e61162f2a2bf414c4e10c45eca98ce5f1aadbd4" From bec471db0ecfb9eb390968d6b07797552e47aa9b Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:03:26 -0600 Subject: [PATCH 10/31] Fix CI install of private asc-contracts via actions/checkout --- .github/actions/install-js-deps/action.yml | 51 +++++++++++++++------- .gitignore | 1 + README.md | 2 +- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/.github/actions/install-js-deps/action.yml b/.github/actions/install-js-deps/action.yml index 948f67f6..31394f3f 100644 --- a/.github/actions/install-js-deps/action.yml +++ b/.github/actions/install-js-deps/action.yml @@ -1,38 +1,57 @@ name: Install JS deps -description: yarn install with authenticated access to private gluwa/asc-contracts on main +description: Install JS deps using gluwa/asc-contracts@main (private; ASC_CONTRACTS_READ_TOKEN) inputs: token: description: GitHub token with Contents:Read on gluwa/asc-contracts (ASC_CONTRACTS_READ_TOKEN) required: true - upgrade_asc_contracts: - description: Re-resolve git+...#main to the current tip (recommended for CI) - required: false - default: "true" runs: using: composite steps: - - name: Authenticate git for gluwa/asc-contracts + - name: Require ASC_CONTRACTS_READ_TOKEN shell: bash env: ASC_CONTRACTS_READ_TOKEN: ${{ inputs.token }} run: | if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on gluwa/asc-contracts)" + echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on https://github.com/gluwa/asc-contracts)" exit 1 fi - # So yarn can resolve git+https://github.com/gluwa/asc-contracts.git#main - git config --global url."https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/".insteadOf "https://github.com/" - - name: yarn install - shell: bash - run: yarn install --ignore-engines + # yarn git+https + insteadOf often still hits unauthenticated ls-remote and gets + # GitHub's "Repository not found" for private repos. actions/checkout applies the PAT. + - name: Checkout gluwa/asc-contracts (main) + uses: actions/checkout@v7 + with: + repository: gluwa/asc-contracts + ref: main + token: ${{ inputs.token }} + path: vendor/asc-contracts + persist-credentials: false - - name: Upgrade @gluwa/usc-contracts to latest main - if: inputs.upgrade_asc_contracts == 'true' + - name: yarn install (file:vendor/asc-contracts) shell: bash run: | - yarn upgrade @gluwa/usc-contracts --ignore-engines - echo "Resolved @gluwa/usc-contracts:" + if [ ! -f vendor/asc-contracts/package.json ]; then + echo "Checkout of gluwa/asc-contracts failed โ€” verify ASC_CONTRACTS_READ_TOKEN has Contents:Read on that repo" + exit 1 + fi + + # package.json keeps git+https for local use; CI installs from the checked-out main tip. + node <<'NODE' + const fs = require('fs'); + const pkgPath = 'package.json'; + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + if (!pkg.dependencies || !pkg.dependencies['@gluwa/usc-contracts']) { + console.error('package.json missing @gluwa/usc-contracts dependency'); + process.exit(1); + } + pkg.dependencies['@gluwa/usc-contracts'] = 'file:vendor/asc-contracts'; + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + NODE + + yarn install --ignore-engines + + echo "Installed @gluwa/usc-contracts from vendor/asc-contracts (gluwa/asc-contracts@main):" yarn list --pattern '@gluwa/usc-contracts' --depth=0 || true ls -la node_modules/@gluwa/usc-contracts/contracts/readability || true ls -la node_modules/@gluwa/usc-contracts/contracts/common || true diff --git a/.gitignore b/.gitignore index 225cdab9..18f05843 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ megalinter-reports/ .env.devnet .tools/ +vendor/ diff --git a/README.md b/README.md index df68b0fa..f8876d9c 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ GitHub Actions cannot fetch `gluwa/asc-contracts` anonymously. Add a repo/org se 1. Create a fine-grained PAT (or GitHub App token) with **Contents: Read** on `gluwa/asc-contracts`. 2. Add secret **`ASC_CONTRACTS_READ_TOKEN`** on this repository (Settings โ†’ Secrets and variables โ†’ Actions). -3. Workflows run `.github/actions/install-js-deps`, which rewrites `https://github.com/` git URLs with that token, runs `yarn install`, then `yarn upgrade @gluwa/usc-contracts` so CI always uses the current `main` tip (`git+https://github.com/gluwa/asc-contracts.git#main`). +3. Workflows run `.github/actions/install-js-deps`, which checkouts [`gluwa/asc-contracts`](https://github.com/gluwa/asc-contracts) `main` with that token into `vendor/asc-contracts`, then `yarn install` via `file:vendor/asc-contracts` (avoids yarnโ€™s unauthenticated git fetch on private repos). Fork PRs will not receive this secret unless you use a trusted workflow pattern. ## Tutorials From 6f74487649df9004a9fd1f1e4ccee2700ded6077 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:53:21 -0600 Subject: [PATCH 11/31] Fix example CI: enable via-IR for ASCMinter deploy Root forge create was stack-too-deep without via_ir; also restore package.json after CI install and ignore private doc links. --- .github/actions/install-js-deps/action.yml | 5 +++++ .github/workflows/bridge-offchain-worker.yml | 9 +++++++++ .github/workflows/custom-contracts-bridging.yml | 9 +++++++++ .github/workflows/hello-bridge.yml | 10 ++++++++++ .github/workflows/loan-flow.yml | 9 +++++++++ .lycheeignore | 4 ++++ README.md | 1 + foundry.toml | 6 +++++- shared/contracts/abi-creator.sh | 0 9 files changed, 52 insertions(+), 1 deletion(-) mode change 100644 => 100755 shared/contracts/abi-creator.sh diff --git a/.github/actions/install-js-deps/action.yml b/.github/actions/install-js-deps/action.yml index 31394f3f..63ef0e58 100644 --- a/.github/actions/install-js-deps/action.yml +++ b/.github/actions/install-js-deps/action.yml @@ -51,7 +51,12 @@ runs: yarn install --ignore-engines + # Restore manifests so later jobs (prettier/check-format) do not see CI-only edits. + git checkout -- package.json yarn.lock 2>/dev/null || true + echo "Installed @gluwa/usc-contracts from vendor/asc-contracts (gluwa/asc-contracts@main):" yarn list --pattern '@gluwa/usc-contracts' --depth=0 || true ls -la node_modules/@gluwa/usc-contracts/contracts/readability || true ls -la node_modules/@gluwa/usc-contracts/contracts/common || true + test -d node_modules/@gluwa/usc-contracts/contracts/readability + test -d node_modules/@gluwa/usc-contracts/contracts/common diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index dcd2b65f..dc346654 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -170,6 +170,8 @@ jobs: - name: Setup id: setup + env: + FOUNDRY_VIA_IR: "true" run: | SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -216,13 +218,20 @@ jobs: DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "INFO: deploy ASCMinter.sol to Creditcoin chain" + set +e OUTPUT=$(~/.foundry/bin/forge create \ --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) + STATUS=$? + set -e echo "$OUTPUT" + if [ "$STATUS" -ne 0 ]; then + echo "ERROR: ASCMinter forge create failed (exit $STATUS)" + exit "$STATUS" + fi ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "asc_minter_contract_address=$ASC_MINTER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index 4ab8c43f..fc8e90ff 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -170,6 +170,8 @@ jobs: - name: Setup id: setup + env: + FOUNDRY_VIA_IR: "true" run: | SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -220,13 +222,20 @@ jobs: git diff echo "INFO: deploy ASCMinter.sol to Creditcoin chain" + set +e OUTPUT=$(~/.foundry/bin/forge create \ --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) + STATUS=$? + set -e echo "$OUTPUT" + if [ "$STATUS" -ne 0 ]; then + echo "ERROR: ASCMinter forge create failed (exit $STATUS)" + exit "$STATUS" + fi ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "asc_minter_contract_address=$ASC_MINTER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index 23edb824..e3c90f7b 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -170,6 +170,9 @@ jobs: - name: Setup id: setup + env: + # ASCMinter + package ASCBase need via-IR (stack too deep otherwise). + FOUNDRY_VIA_IR: "true" run: | SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -216,13 +219,20 @@ jobs: DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "INFO: deploy ASCMinter.sol to Creditcoin chain" + set +e OUTPUT=$(~/.foundry/bin/forge create \ --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) + STATUS=$? + set -e echo "$OUTPUT" + if [ "$STATUS" -ne 0 ]; then + echo "ERROR: ASCMinter forge create failed (exit $STATUS)" + exit "$STATUS" + fi ASC_MINTER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "minter_contract_address=$ASC_MINTER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index efcfbf19..01485f27 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -170,6 +170,8 @@ jobs: - name: Setup id: setup + env: + FOUNDRY_VIA_IR: "true" run: | SOURCE_CHAIN_RPC_URL="${{ steps.env.outputs.source_chain_rpc_url }}" @@ -226,13 +228,20 @@ jobs: DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "INFO: deploy ASCLoanManager.sol to Creditcoin" + set +e OUTPUT=$(~/.foundry/bin/forge create \ --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager 2>&1) + STATUS=$? + set -e echo "$OUTPUT" + if [ "$STATUS" -ne 0 ]; then + echo "ERROR: ASCLoanManager forge create failed (exit $STATUS)" + exit "$STATUS" + fi ASC_LOAN_MANAGER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "asc_loan_manager_contract_address=$ASC_LOAN_MANAGER_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" diff --git a/.lycheeignore b/.lycheeignore index 5a2bdabf..94fd1f5a 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -25,4 +25,8 @@ https://developer.metamask.io(.*) https://(.*).creditcoin.network/ https://blockchain.googleapis.com/v1/projects/(.*) https://github.com/gluwa/usc-contracts(.*) +https://github.com/gluwa/asc-contracts(.*) https://docs.creditcoin.org/attestcoin-protocol(.*) +https://docs.attestcoin.org(.*) +https://www.npmjs.com/package/@gluwa/usc-contracts(.*) +https://www.npmjs.com/package/@gluwa/asc-contracts(.*) diff --git a/README.md b/README.md index f8876d9c..d09c58e0 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ GitHub Actions cannot fetch `gluwa/asc-contracts` anonymously. Add a repo/org se 3. Workflows run `.github/actions/install-js-deps`, which checkouts [`gluwa/asc-contracts`](https://github.com/gluwa/asc-contracts) `main` with that token into `vendor/asc-contracts`, then `yarn install` via `file:vendor/asc-contracts` (avoids yarnโ€™s unauthenticated git fetch on private repos). Fork PRs will not receive this secret unless you use a trusted workflow pattern. + ## Tutorials We recommend going through them in this order: diff --git a/foundry.toml b/foundry.toml index 22d97e9c..40eff86f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,6 +3,7 @@ out = "out" src = "bridge/contracts/sol" libs = ["node_modules"] # Used when forge create is run from the repo root (example CI workflows). +# via_ir required: ASCMinter + package ASCBase hit "stack too deep" without it. remappings = [ "@openzeppelin/=node_modules/@openzeppelin/", "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/", @@ -10,6 +11,9 @@ remappings = [ solc_version = "0.8.30" optimizer = true optimizer_runs = 200 -via_ir = false +via_ir = true evm_version = "shanghai" verbosity = 2 + +[lint] +lint_on_build = false diff --git a/shared/contracts/abi-creator.sh b/shared/contracts/abi-creator.sh old mode 100644 new mode 100755 From 3586bc1f4b72a284e3e22524929286c083fdc9f1 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:58:31 -0600 Subject: [PATCH 12/31] Ignore vendor/asc-contracts in eslint and typecheck CI checkouts the private contracts repo into vendor/, which was being linted and typechecked. --- .eslintrc.js | 1 + .github/actions/install-js-deps/action.yml | 4 ++++ .prettierignore | 3 +++ package.json | 2 +- tsconfig.json | 2 +- 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8387e6e5..c31d33b7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -32,6 +32,7 @@ module.exports = { // "eslint-plugin-react", '@typescript-eslint', ], + ignorePatterns: ['vendor/**', 'node_modules/**', 'dist/**', 'lib/**', 'out/**'], rules: { '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/array-type': [ diff --git a/.github/actions/install-js-deps/action.yml b/.github/actions/install-js-deps/action.yml index 63ef0e58..c96bf395 100644 --- a/.github/actions/install-js-deps/action.yml +++ b/.github/actions/install-js-deps/action.yml @@ -27,6 +27,10 @@ runs: token: ${{ inputs.token }} path: vendor/asc-contracts persist-credentials: false + sparse-checkout: | + contracts + package.json + sparse-checkout-cone-mode: false - name: yarn install (file:vendor/asc-contracts) shell: bash diff --git a/.prettierignore b/.prettierignore index ecb28d00..a10ebb18 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,5 +10,8 @@ node_modules # Package-lock package-lock.json +# CI checkout of gluwa/asc-contracts +vendor + # TS Config tsconfig.json diff --git a/package.json b/package.json index 4477f9ff..0d8a0d9c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "utils:check_balance": "node shared/utils/check_balance.js", "utils:check_setup": "tsx shared/check_setup.ts", "check-format": "prettier --check .", - "eslint": "eslint -c .eslintrc.js --ignore-pattern .eslintrc.js --ignore-pattern check_balance.js --max-warnings 0 --ext .ts --ext .js .", + "eslint": "eslint -c .eslintrc.js --ignore-pattern .eslintrc.js --ignore-pattern check_balance.js --ignore-pattern vendor/** --max-warnings 0 --ext .ts --ext .js .", "format": "prettier --write .", "build": "forge build --root bridge && forge build --root loan", "test": "forge test --root bridge && forge test --root loan", diff --git a/tsconfig.json b/tsconfig.json index b3ec2195..7f6ab8de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true }, - "exclude": ["node_modules", "**/__tests__/*", "dist"], + "exclude": ["node_modules", "**/__tests__/*", "dist", "vendor"], "ts-node": { "require": ["tsconfig-paths/register"], From a9d57f399a0392f183e87b4e160c19ef6ec90333 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:27:07 -0600 Subject: [PATCH 13/31] Use npm package and remove PAT to access asc-contracts github repo directly --- .github/actions/install-js-deps/action.yml | 66 ------------------- .github/workflows/bridge-offchain-worker.yml | 10 ++- .../workflows/custom-contracts-bridging.yml | 10 ++- .github/workflows/hello-bridge.yml | 10 ++- .github/workflows/javascript-checks.yaml | 6 +- .github/workflows/loan-flow.yml | 10 ++- .github/workflows/solidity.yml | 18 ++--- .lycheeignore | 1 - .markdownlint.yaml | 8 +++ .mega-linter.yml | 2 + README.md | 22 ++----- VERIFICATION.md | 2 +- bridge/README.md | 8 +-- bridge/contracts/Contributor Notes.md | 6 +- bridge/contracts/abi-creator.sh | 2 +- bridge/contracts/sol/ASCMinter.sol | 4 +- bridge/custom-contracts-bridging/README.md | 6 +- bridge/foundry.toml | 10 +-- bridge/test/ASCMinterBurnLog.t.sol | 2 +- bridge/test/harness/ASCMinterHarness.sol | 2 +- foundry.toml | 2 +- loan/contracts/abi-creator.sh | 2 +- loan/contracts/abi_generation.md | 2 +- loan/contracts/sol/ASCLoanManager.sol | 4 +- loan/foundry.toml | 10 +-- loan/scripts/README.md | 5 +- loan/test/ASCLoanManagerSourceBinding.t.sol | 2 +- loan/test/harness/ASCLoanManagerHarness.sol | 2 +- package.json | 6 +- yarn.lock | 7 +- 30 files changed, 82 insertions(+), 165 deletions(-) delete mode 100644 .github/actions/install-js-deps/action.yml create mode 100644 .markdownlint.yaml diff --git a/.github/actions/install-js-deps/action.yml b/.github/actions/install-js-deps/action.yml deleted file mode 100644 index c96bf395..00000000 --- a/.github/actions/install-js-deps/action.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Install JS deps -description: Install JS deps using gluwa/asc-contracts@main (private; ASC_CONTRACTS_READ_TOKEN) -inputs: - token: - description: GitHub token with Contents:Read on gluwa/asc-contracts (ASC_CONTRACTS_READ_TOKEN) - required: true -runs: - using: composite - steps: - - name: Require ASC_CONTRACTS_READ_TOKEN - shell: bash - env: - ASC_CONTRACTS_READ_TOKEN: ${{ inputs.token }} - run: | - if [ -z "${ASC_CONTRACTS_READ_TOKEN}" ]; then - echo "Missing secret ASC_CONTRACTS_READ_TOKEN (Contents:Read on https://github.com/gluwa/asc-contracts)" - exit 1 - fi - - # yarn git+https + insteadOf often still hits unauthenticated ls-remote and gets - # GitHub's "Repository not found" for private repos. actions/checkout applies the PAT. - - name: Checkout gluwa/asc-contracts (main) - uses: actions/checkout@v7 - with: - repository: gluwa/asc-contracts - ref: main - token: ${{ inputs.token }} - path: vendor/asc-contracts - persist-credentials: false - sparse-checkout: | - contracts - package.json - sparse-checkout-cone-mode: false - - - name: yarn install (file:vendor/asc-contracts) - shell: bash - run: | - if [ ! -f vendor/asc-contracts/package.json ]; then - echo "Checkout of gluwa/asc-contracts failed โ€” verify ASC_CONTRACTS_READ_TOKEN has Contents:Read on that repo" - exit 1 - fi - - # package.json keeps git+https for local use; CI installs from the checked-out main tip. - node <<'NODE' - const fs = require('fs'); - const pkgPath = 'package.json'; - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - if (!pkg.dependencies || !pkg.dependencies['@gluwa/usc-contracts']) { - console.error('package.json missing @gluwa/usc-contracts dependency'); - process.exit(1); - } - pkg.dependencies['@gluwa/usc-contracts'] = 'file:vendor/asc-contracts'; - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - NODE - - yarn install --ignore-engines - - # Restore manifests so later jobs (prettier/check-format) do not see CI-only edits. - git checkout -- package.json yarn.lock 2>/dev/null || true - - echo "Installed @gluwa/usc-contracts from vendor/asc-contracts (gluwa/asc-contracts@main):" - yarn list --pattern '@gluwa/usc-contracts' --depth=0 || true - ls -la node_modules/@gluwa/usc-contracts/contracts/readability || true - ls -la node_modules/@gluwa/usc-contracts/contracts/common || true - test -d node_modules/@gluwa/usc-contracts/contracts/readability - test -d node_modules/@gluwa/usc-contracts/contracts/common diff --git a/.github/workflows/bridge-offchain-worker.yml b/.github/workflows/bridge-offchain-worker.yml index dc346654..4d94340b 100644 --- a/.github/workflows/bridge-offchain-worker.yml +++ b/.github/workflows/bridge-offchain-worker.yml @@ -163,10 +163,8 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Setup id: setup @@ -213,7 +211,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -223,7 +221,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) STATUS=$? set -e diff --git a/.github/workflows/custom-contracts-bridging.yml b/.github/workflows/custom-contracts-bridging.yml index fc8e90ff..baf6b671 100644 --- a/.github/workflows/custom-contracts-bridging.yml +++ b/.github/workflows/custom-contracts-bridging.yml @@ -163,10 +163,8 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Setup id: setup @@ -213,7 +211,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -227,7 +225,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) STATUS=$? set -e diff --git a/.github/workflows/hello-bridge.yml b/.github/workflows/hello-bridge.yml index e3c90f7b..c253a0e3 100644 --- a/.github/workflows/hello-bridge.yml +++ b/.github/workflows/hello-bridge.yml @@ -163,10 +163,8 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Setup id: setup @@ -214,7 +212,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ - node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -224,7 +222,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key ${{ steps.env.outputs.execution_chain_private_key }} \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ bridge/contracts/sol/ASCMinter.sol:ASCMinter 2>&1) STATUS=$? set -e diff --git a/.github/workflows/javascript-checks.yaml b/.github/workflows/javascript-checks.yaml index fd19e2aa..80f119cc 100644 --- a/.github/workflows/javascript-checks.yaml +++ b/.github/workflows/javascript-checks.yaml @@ -43,10 +43,8 @@ jobs: - name: Install forge-std run: git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Execute command ${{ matrix.command }} run: | diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 01485f27..4c7919c8 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -163,10 +163,8 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Setup id: setup @@ -223,7 +221,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) + node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder 2>&1) echo "$OUTPUT" DECODER_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") @@ -233,7 +231,7 @@ jobs: --broadcast \ --rpc-url "$CREDITCOIN_RPC_URL" \ --private-key "$WALLET_PK" \ - --libraries "node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ + --libraries "node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$DECODER_CONTRACT_ADDRESS" \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager 2>&1) STATUS=$? set -e diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index e680c576..9692f189 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -33,10 +33,8 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Compile and test smart contracts run: | @@ -44,9 +42,9 @@ jobs: ~/.foundry/bin/forge remappings --root bridge || true echo "INFO: forge remappings (loan)" ~/.foundry/bin/forge remappings --root loan || true - ls -la node_modules/@gluwa/usc-contracts/contracts || true - ls -la node_modules/@gluwa/usc-contracts/contracts/readability || true - ls -la node_modules/@gluwa/usc-contracts/contracts/common || true + ls -la node_modules/@gluwa/asc-contracts/contracts || true + ls -la node_modules/@gluwa/asc-contracts/contracts/readability || true + ls -la node_modules/@gluwa/asc-contracts/contracts/common || true ~/.foundry/bin/forge build --root bridge ~/.foundry/bin/forge build --root loan @@ -69,10 +67,8 @@ jobs: cache: 'yarn' - run: npm install -g yarn - - name: Install dependencies (asc-contracts from GitHub main) - uses: ./.github/actions/install-js-deps - with: - token: ${{ secrets.ASC_CONTRACTS_READ_TOKEN }} + - name: Install dependencies + run: yarn install --ignore-engines - name: Regenerate the ABI run: | diff --git a/.lycheeignore b/.lycheeignore index 94fd1f5a..a914fbe6 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -29,4 +29,3 @@ https://github.com/gluwa/asc-contracts(.*) https://docs.creditcoin.org/attestcoin-protocol(.*) https://docs.attestcoin.org(.*) https://www.npmjs.com/package/@gluwa/usc-contracts(.*) -https://www.npmjs.com/package/@gluwa/asc-contracts(.*) diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..1fd03c2a --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,8 @@ +# Tutorial docs: long lines, optional link defs, and tree fences are intentional. +default: true +MD013: false +MD028: false +MD033: false +MD040: false +MD041: false +MD053: false diff --git a/.mega-linter.yml b/.mega-linter.yml index 9c2adaf4..43faf870 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -26,5 +26,7 @@ DISABLE_LINTERS: - TYPESCRIPT_ES - TYPESCRIPT_STANDARD - SPELL_CSPELL + # Tutorial markdown uses long command/URL lines; table formatter is noisy on existing docs. + - MARKDOWN_MARKDOWN_TABLE_FORMATTER SHOW_ELAPSED_TIME: true diff --git a/README.md b/README.md index d09c58e0..457e832f 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCMinter` on shared package `ASCBase`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). -`@gluwa/usc-contracts` is installed from GitHub [`gluwa/asc-contracts`](https://github.com/gluwa/asc-contracts) `main` (`git+https://github.com/gluwa/asc-contracts.git#main`). CI authenticates with secret `ASC_CONTRACTS_READ_TOKEN` (see [CI setup](#ci-private-asc-contracts)). +`@gluwa/asc-contracts` is installed from npm ([`@gluwa/asc-contracts`](https://www.npmjs.com/package/@gluwa/asc-contracts)). Learn how to use the Creditcoin Decentralized Oracle through guided tutorials where you set up and interact with your own decentralized bridge and loan examples. ## Repository layout -``` +```text usc-testnet-bridge-examples/ -โ”œโ”€โ”€ bridge/ # Simplified ASC bridge tutorials + ASCMinter (ASCBase from @gluwa/usc-contracts) -โ”‚ โ”œโ”€โ”€ contracts/ # Example ASC apps; ASCBase + EvmV1Decoder from @gluwa/usc-contracts +โ”œโ”€โ”€ bridge/ # Simplified ASC bridge tutorials + ASCMinter (ASCBase from @gluwa/asc-contracts) +โ”‚ โ”œโ”€โ”€ contracts/ # Example ASC apps; ASCBase + EvmV1Decoder from @gluwa/asc-contracts โ”‚ โ”œโ”€โ”€ hello-bridge/ โ”‚ โ”œโ”€โ”€ custom-contracts-bridging/ โ”‚ โ””โ”€โ”€ bridge-offchain-worker/ @@ -32,11 +32,9 @@ Before attempting any of the tutorials, make sure the following are installed: - [yarn] - [foundry] -Install dependencies from the repository root (needs read access to private `gluwa/asc-contracts`): +Install dependencies from the repository root: ```sh -# Optional if HTTPS clone of gluwa/asc-contracts fails without auth: -# git config --global url."https://x-access-token:${ASC_CONTRACTS_READ_TOKEN}@github.com/".insteadOf "https://github.com/" yarn ``` @@ -69,16 +67,6 @@ Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ Full verification checklist: [VERIFICATION.md](./VERIFICATION.md). -### CI: private `asc-contracts` - -GitHub Actions cannot fetch `gluwa/asc-contracts` anonymously. Add a repo/org secret: - -1. Create a fine-grained PAT (or GitHub App token) with **Contents: Read** on `gluwa/asc-contracts`. -2. Add secret **`ASC_CONTRACTS_READ_TOKEN`** on this repository (Settings โ†’ Secrets and variables โ†’ Actions). -3. Workflows run `.github/actions/install-js-deps`, which checkouts [`gluwa/asc-contracts`](https://github.com/gluwa/asc-contracts) `main` with that token into `vendor/asc-contracts`, then `yarn install` via `file:vendor/asc-contracts` (avoids yarnโ€™s unauthenticated git fetch on private repos). - -Fork PRs will not receive this secret unless you use a trusted workflow pattern. - ## Tutorials We recommend going through them in this order: diff --git a/VERIFICATION.md b/VERIFICATION.md index 6f540277..56c16677 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -4,7 +4,7 @@ Use this to validate the repository locally before release or deployment. ## Automated (CI + local) -From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH`). Bridge and loan both inherit readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts` (`contracts/readability/`, `contracts/common/`): +From the repository root (requires [Foundry](https://getfoundry.sh/) on your `PATH`). Bridge and loan both inherit readability `ASCBase` and `EvmV1Decoder` from `@gluwa/asc-contracts` (`contracts/readability/`, `contracts/common/`): ```sh yarn diff --git a/bridge/README.md b/bridge/README.md index 1ad9d3b0..8213a425 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -1,6 +1,6 @@ # Bridge examples (simplified ASC bridge) -These tutorials implement a **simplified readability bridge** โ€” not the production write-ability stack (Outbox, Relayer, Inbox, liquidity operators, etc.) published as [`@gluwa/usc-contracts`](https://www.npmjs.com/package/@gluwa/usc-contracts). +These tutorials implement a **simplified readability bridge** โ€” not the production write-ability stack (Outbox, Relayer, Inbox, liquidity operators, etc.) published as [`@gluwa/asc-contracts`](https://www.npmjs.com/package/@gluwa/asc-contracts). This example follows **[Attestcoin Readability โ€” Building a Bridge Minter (ยง6.4)](https://docs.attestcoin.org/attestcoin-protocol/attestcoin-readability)** (inherit `ASCBase`, prove a source-chain burn, mint wrapped tokens on Creditcoin). @@ -8,9 +8,9 @@ This example follows **[Attestcoin Readability โ€” Building a Bridge Minter (ยง6 | Piece | Role | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | -| **`ASCBase`** (from `@gluwa/usc-contracts`) | Shared readability base: verify via native precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. Same base as the loan example. | +| **`ASCBase`** (from `@gluwa/asc-contracts`) | Shared readability base: verify via native precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. Same base as the loan example. | | **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens **only if the source-chain emitter is whitelisted** via `wrapOriginToken`. | -| **`EvmV1Decoder`** (from `@gluwa/usc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/common/`). Not the messaging bridge. | +| **`EvmV1Decoder`** (from `@gluwa/asc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/common/`). Not the messaging bridge. | | **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint** (mint requires the burn contract address to be a whitelisted emitter). @@ -23,7 +23,7 @@ Anyone with a valid proof can submit `execute`, but the ASC still rejects burns - `ASCBridgeLiquidityOperator`, `ClientBridgeLiquidityOperator`, or other write-ability bridge contracts - Trusted relayers or operator-gated mint paths -For the production bridge architecture, see [`@gluwa/usc-contracts`](https://www.npmjs.com/package/@gluwa/usc-contracts) and Creditcoin's write-ability documentation. +For the production bridge architecture, see [`@gluwa/asc-contracts`](https://www.npmjs.com/package/@gluwa/asc-contracts) and Creditcoin's write-ability documentation. ## Layout diff --git a/bridge/contracts/Contributor Notes.md b/bridge/contracts/Contributor Notes.md index 0b052d4b..c1a3c4ad 100644 --- a/bridge/contracts/Contributor Notes.md +++ b/bridge/contracts/Contributor Notes.md @@ -13,7 +13,7 @@ yarn For loan contracts: `./loan/contracts/abi-creator.sh`. -Bridge example contracts (`ASCMinter`, tokens) inherit shared readability `ASCBase` from `@gluwa/usc-contracts` (`contracts/readability/ASCBase.sol`). `EvmV1Decoder` comes from the same package (`contracts/common/`). +Bridge example contracts (`ASCMinter`, tokens) inherit shared readability `ASCBase` from `@gluwa/asc-contracts` (`contracts/readability/ASCBase.sol`). `EvmV1Decoder` comes from the same package (`contracts/common/`). `ASCMinter` mints only when the burn logโ€™s emitting address is in `whitelistedEmitters` (set by `wrapOriginToken`). Regenerate ABIs after changing that surface. @@ -44,9 +44,9 @@ cast balance --rpc-url $CREDITCOIN_RPC_URL Ensure `--libraries` uses the deployed decoder address: ```bash ---libraries node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder: +--libraries node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder: ``` -The library is published in npm at `contracts/common/EvmV1Decoder.sol`. Tutorials and Solidity imports both use that path via `@gluwa/usc-contracts` from `node_modules`. +The library is published in npm at `contracts/common/EvmV1Decoder.sol`. Tutorials and Solidity imports both use that path via `@gluwa/asc-contracts` from `node_modules`. After deploy, submit proofs with `yarn hello_bridge:submit_query` or `yarn custom_bridge:submit_query`. diff --git a/bridge/contracts/abi-creator.sh b/bridge/contracts/abi-creator.sh index a9610e82..99d17f93 100755 --- a/bridge/contracts/abi-creator.sh +++ b/bridge/contracts/abi-creator.sh @@ -23,7 +23,7 @@ for p in "$sol_directory"/*.sol; do file_with_extension="$contract_name.json" solc --base-path . --include-path "node_modules" \ "@openzeppelin/=node_modules/@openzeppelin/" \ - "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/" \ + "@gluwa/asc-contracts/=node_modules/@gluwa/asc-contracts/" \ "$p" \ --combined-json abi --overwrite --json-indent 2 | \ extract_abi "$p" "$contract_name" | jq --indent 2 . > "$abi_directory/$file_with_extension" diff --git a/bridge/contracts/sol/ASCMinter.sol b/bridge/contracts/sol/ASCMinter.sol index 40f12bde..5c915005 100644 --- a/bridge/contracts/sol/ASCMinter.sol +++ b/bridge/contracts/sol/ASCMinter.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.23; import {ASCMintableToken, ASC_MINTER} from "./MintableToken.sol"; -import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {ASCBase} from "@gluwa/asc-contracts/contracts/readability/ASCBase.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; /** * @title ASCMinter diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index 44759a46..e76b5580 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -1,7 +1,7 @@ # Custom Contract Bridging > [!NOTE] -> Deploys the **simplified** `ASCMinter` on shared package `ASCBase` (`@gluwa/usc-contracts` readability) โ€” direct `execute` minting, no relayer contracts. See [bridge/README.md](../README.md). +> Deploys the **simplified** `ASCMinter` on shared package `ASCBase` (`@gluwa/asc-contracts` readability) โ€” direct `execute` minting, no relayer contracts. See [bridge/README.md](../README.md). > [!TIP] > This tutorial builds on the previous [Hello Bridge] example -make sure to check it out before @@ -81,7 +81,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder ``` Add the library address to your root `.env` so later steps (and the loan tutorial) can reuse it: @@ -105,7 +105,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ + --libraries node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` diff --git a/bridge/foundry.toml b/bridge/foundry.toml index e3f80ffc..4a149a39 100644 --- a/bridge/foundry.toml +++ b/bridge/foundry.toml @@ -3,13 +3,13 @@ out = "out" src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] -# Resolve @gluwa/usc-contracts from yarn (readability ASCBase + common/EvmV1Decoder). -# Package source is gluwa/asc-contracts (git #main via yarn); remapped under this alias. +# Resolve @gluwa/asc-contracts from yarn/npm (readability ASCBase + common/EvmV1Decoder). +# Package: https://www.npmjs.com/package/@gluwa/asc-contracts allow_paths = ["../node_modules", "../lib"] remappings = [ - "@openzeppelin/=../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", - "forge-std/=../lib/forge-std/src/", + "@openzeppelin/=../node_modules/@openzeppelin/", + "@gluwa/asc-contracts/=../node_modules/@gluwa/asc-contracts/", + "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" optimizer = true diff --git a/bridge/test/ASCMinterBurnLog.t.sol b/bridge/test/ASCMinterBurnLog.t.sol index f2668d2f..a16a35cf 100644 --- a/bridge/test/ASCMinterBurnLog.t.sol +++ b/bridge/test/ASCMinterBurnLog.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import {Test} from "forge-std/Test.sol"; import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; contract ASCMinterBurnLogTest is Test { ASCMinterHarness internal minter; diff --git a/bridge/test/harness/ASCMinterHarness.sol b/bridge/test/harness/ASCMinterHarness.sol index b14b439c..969f0f52 100644 --- a/bridge/test/harness/ASCMinterHarness.sol +++ b/bridge/test/harness/ASCMinterHarness.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; contract ASCMinterHarness is ASCMinter { function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) diff --git a/foundry.toml b/foundry.toml index 40eff86f..853acf45 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,7 @@ libs = ["node_modules"] # via_ir required: ASCMinter + package ASCBase hit "stack too deep" without it. remappings = [ "@openzeppelin/=node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/", + "@gluwa/asc-contracts/=node_modules/@gluwa/asc-contracts/", ] solc_version = "0.8.30" optimizer = true diff --git a/loan/contracts/abi-creator.sh b/loan/contracts/abi-creator.sh index 913000e8..af6f40ee 100755 --- a/loan/contracts/abi-creator.sh +++ b/loan/contracts/abi-creator.sh @@ -23,7 +23,7 @@ for p in "$sol_directory"/*.sol; do file_with_extension="$contract_name.json" solc --base-path . --include-path "node_modules" \ "@openzeppelin/=node_modules/@openzeppelin/" \ - "@gluwa/usc-contracts/=node_modules/@gluwa/usc-contracts/" \ + "@gluwa/asc-contracts/=node_modules/@gluwa/asc-contracts/" \ "$p" \ --combined-json abi --overwrite --json-indent 2 | \ extract_abi "$p" "$contract_name" | jq --indent 2 . > "$abi_directory/$file_with_extension" diff --git a/loan/contracts/abi_generation.md b/loan/contracts/abi_generation.md index fd6da7e2..cd67fe6d 100644 --- a/loan/contracts/abi_generation.md +++ b/loan/contracts/abi_generation.md @@ -7,4 +7,4 @@ yarn ./loan/contracts/abi-creator.sh ``` -Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/usc-contracts` (same base as the bridge examples). Shared source-chain `TestERC20` lives under `shared/contracts/`. ABI regeneration: see [bridge/contracts/Contributor Notes.md](../../bridge/contracts/Contributor%20Notes.md) and `./shared/contracts/abi-creator.sh`. +Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/asc-contracts` (same base as the bridge examples). Shared source-chain `TestERC20` lives under `shared/contracts/`. ABI regeneration: see [bridge/contracts/Contributor Notes.md](../../bridge/contracts/Contributor%20Notes.md) and `./shared/contracts/abi-creator.sh`. diff --git a/loan/contracts/sol/ASCLoanManager.sol b/loan/contracts/sol/ASCLoanManager.sol index 27c4989e..cc3d85f7 100644 --- a/loan/contracts/sol/ASCLoanManager.sol +++ b/loan/contracts/sol/ASCLoanManager.sol @@ -7,8 +7,8 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {LoanFlow, LoanStatus, LoanOrder, LoanTerms} from "./LoanTypes.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; -import {ASCBase} from "@gluwa/usc-contracts/contracts/readability/ASCBase.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; +import {ASCBase} from "@gluwa/asc-contracts/contracts/readability/ASCBase.sol"; /** * @title ASCLoanManager diff --git a/loan/foundry.toml b/loan/foundry.toml index 3263e78a..d50b158c 100644 --- a/loan/foundry.toml +++ b/loan/foundry.toml @@ -3,13 +3,13 @@ out = "out" src = "contracts/sol" test = "test" libs = ["../node_modules", "../lib"] -# Resolve @gluwa/usc-contracts from yarn (readability ASCBase + common/EvmV1Decoder). -# Same package alias as bridge โ€” one ASCBase for both examples. +# Resolve @gluwa/asc-contracts from yarn/npm (readability ASCBase + common/EvmV1Decoder). +# Same package as bridge โ€” one ASCBase for both examples. allow_paths = ["../node_modules", "../lib"] remappings = [ - "@openzeppelin/=../node_modules/@openzeppelin/", - "@gluwa/usc-contracts/=../node_modules/@gluwa/usc-contracts/", - "forge-std/=../lib/forge-std/src/", + "@openzeppelin/=../node_modules/@openzeppelin/", + "@gluwa/asc-contracts/=../node_modules/@gluwa/asc-contracts/", + "forge-std/=../lib/forge-std/src/", ] solc_version = "0.8.30" optimizer = true diff --git a/loan/scripts/README.md b/loan/scripts/README.md index 963ab450..efe4f5d0 100644 --- a/loan/scripts/README.md +++ b/loan/scripts/README.md @@ -78,7 +78,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder + node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder ``` Save the address in `.env`: @@ -94,7 +94,7 @@ forge create \ --broadcast \ --rpc-url $CREDITCOIN_RPC_URL \ --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - --libraries node_modules/@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ + --libraries node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder:$EVM_V1_DECODER_LIBRARY_ADDRESS \ loan/contracts/sol/ASCLoanManager.sol:ASCLoanManager ``` @@ -499,7 +499,6 @@ You've learned: If you haven't already, take a look at the [ASC Gitbook] for more information. -[Bridge Offchain Worker]: ../../bridge/bridge-offchain-worker/README.md [Hello Bridge]: ../../bridge/hello-bridge/README.md#11-generate-a-new-wallet-address [ASC Gitbook]: https://docs.attestcoin.org/attestcoin-protocol [๐Ÿšฐ testnet faucet]: https://cloud.google.com/application/web3/faucet/ethereum/sepolia diff --git a/loan/test/ASCLoanManagerSourceBinding.t.sol b/loan/test/ASCLoanManagerSourceBinding.t.sol index a1173388..3e810b49 100644 --- a/loan/test/ASCLoanManagerSourceBinding.t.sol +++ b/loan/test/ASCLoanManagerSourceBinding.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ASCLoanManagerHarness} from "./harness/ASCLoanManagerHarness.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; /// @notice Regression tests for loan source-contract binding โ€” fund/repay logs must come from the registered source contract. contract ASCLoanManagerSourceBindingTest is Test { diff --git a/loan/test/harness/ASCLoanManagerHarness.sol b/loan/test/harness/ASCLoanManagerHarness.sol index 8afbc274..efe93c6e 100644 --- a/loan/test/harness/ASCLoanManagerHarness.sol +++ b/loan/test/harness/ASCLoanManagerHarness.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {ASCLoanManager} from "../../contracts/sol/ASCLoanManager.sol"; -import {EvmV1Decoder} from "@gluwa/usc-contracts/contracts/common/EvmV1Decoder.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; /// @dev Test harness exposing internal log validators for source-contract binding tests. contract ASCLoanManagerHarness is ASCLoanManager { diff --git a/package.json b/package.json index 0d8a0d9c..8a37ced9 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,11 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", "prettier": "^3.9.4", - "typescript": "6.0.2", - "tsx": "^4.22.4" + "tsx": "^4.22.4", + "typescript": "6.0.2" }, "dependencies": { - "@gluwa/usc-contracts": "git+https://github.com/gluwa/asc-contracts.git#main", + "@gluwa/asc-contracts": "0.2.1", "@gluwa/usc-sdk": "0.18.0", "@openzeppelin/contracts": "5.4.0", "@types/node": "26.1.0", diff --git a/yarn.lock b/yarn.lock index 640f082a..109d1726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -169,9 +169,10 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@gluwa/usc-contracts@git+https://github.com/gluwa/asc-contracts.git#main": - version "0.2.0" - resolved "git+https://github.com/gluwa/asc-contracts.git#6d49d0833a87d09fdcc56cb932de672b90c29c12" +"@gluwa/asc-contracts@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@gluwa/asc-contracts/-/asc-contracts-0.2.1.tgz#f88e9355c613f46073f626b7b04f8f3223480097" + integrity sha512-3/zVx8ElFewTB0Xy06xJ/uPYfvFI1usOe/ilxoM5SNT9Cf47ML/YW41Pq00NttxuOPLkh8HnRlPgD1n66ritLA== dependencies: "@openzeppelin/contracts" "5.1.0" From c82ed8cf1becb628016467d780166c2fc0dade3d Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:32:04 -0600 Subject: [PATCH 14/31] fix linter ignore path with correct package --- .lycheeignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lycheeignore b/.lycheeignore index a914fbe6..2384935f 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -28,4 +28,4 @@ https://github.com/gluwa/usc-contracts(.*) https://github.com/gluwa/asc-contracts(.*) https://docs.creditcoin.org/attestcoin-protocol(.*) https://docs.attestcoin.org(.*) -https://www.npmjs.com/package/@gluwa/usc-contracts(.*) +https://www.npmjs.com/package/@gluwa/asc-contracts(.*) From 78663163fa714def29c214083d48fa2ff50a04e2 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:50:54 -0600 Subject: [PATCH 15/31] Renaming md file to make it standardized --- bridge/README.md | 2 +- bridge/contracts/ABI_GENERATION.md | 3 +++ .../contracts/{Contributor Notes.md => CONTRIBUTOR_NOTES.md} | 0 bridge/contracts/DEPLOYMENT.md | 2 +- bridge/contracts/abi_generation.md | 3 --- bridge/custom-contracts-bridging/README.md | 4 ++-- loan/contracts/{abi_generation.md => ABI_GENERATION.md} | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 bridge/contracts/ABI_GENERATION.md rename bridge/contracts/{Contributor Notes.md => CONTRIBUTOR_NOTES.md} (100%) delete mode 100644 bridge/contracts/abi_generation.md rename loan/contracts/{abi_generation.md => ABI_GENERATION.md} (70%) diff --git a/bridge/README.md b/bridge/README.md index 8213a425..be19e81c 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -41,7 +41,7 @@ bridge/ 2. [Custom Contracts Bridging](./custom-contracts-bridging/README.md) 3. [Bridge Offchain Worker](./bridge-offchain-worker/README.md) -Deployment troubleshooting: [contracts/Contributor Notes.md](./contracts/Contributor%20Notes.md). +Deployment troubleshooting: [contracts/CONTRIBUTOR_NOTES.md](./contracts/CONTRIBUTOR_NOTES.md). ## Before you run tutorials diff --git a/bridge/contracts/ABI_GENERATION.md b/bridge/contracts/ABI_GENERATION.md new file mode 100644 index 00000000..2cf83ef2 --- /dev/null +++ b/bridge/contracts/ABI_GENERATION.md @@ -0,0 +1,3 @@ +# ABI generation + +Moved to [CONTRIBUTOR_NOTES.md](./CONTRIBUTOR_NOTES.md). diff --git a/bridge/contracts/Contributor Notes.md b/bridge/contracts/CONTRIBUTOR_NOTES.md similarity index 100% rename from bridge/contracts/Contributor Notes.md rename to bridge/contracts/CONTRIBUTOR_NOTES.md diff --git a/bridge/contracts/DEPLOYMENT.md b/bridge/contracts/DEPLOYMENT.md index 0a32b1db..468ea5be 100644 --- a/bridge/contracts/DEPLOYMENT.md +++ b/bridge/contracts/DEPLOYMENT.md @@ -1,5 +1,5 @@ # Deployment troubleshooting -Moved to [Contributor Notes.md](./Contributor%20Notes.md). +Moved to [CONTRIBUTOR_NOTES.md](./CONTRIBUTOR_NOTES.md). Tutorial deploy steps live in [custom-contracts-bridging/README.md](../custom-contracts-bridging/README.md). diff --git a/bridge/contracts/abi_generation.md b/bridge/contracts/abi_generation.md deleted file mode 100644 index c0948825..00000000 --- a/bridge/contracts/abi_generation.md +++ /dev/null @@ -1,3 +0,0 @@ -# ABI generation - -Moved to [Contributor Notes.md](./Contributor%20Notes.md). diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index e76b5580..128f006e 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -109,7 +109,7 @@ forge create \ bridge/contracts/sol/ASCMinter.sol:ASCMinter ``` -If deployment fails (nonce, gas, etc.), see [Contributor deploy notes](../contracts/Contributor%20Notes.md). +If deployment fails (nonce, gas, etc.), see [Contributor deploy notes](../contracts/CONTRIBUTOR_NOTES.md). You should get output with the contract address: @@ -282,5 +282,5 @@ Next: [Loan Flow](../../loan/scripts/README.md) reuses your decoder library and [step 2]: #2-deploy-a-test-erc20-contract-on-sepolia [step 3.2]: #32-deploy-ascminter [step 5]: #5-submit-a-mint-query-to-the-asc-contract -[Contributor deploy notes]: ../contracts/Contributor%20Notes.md +[Contributor deploy notes]: ../contracts/CONTRIBUTOR_NOTES.md [Bridge Offchain Worker]: ../bridge-offchain-worker/README.md diff --git a/loan/contracts/abi_generation.md b/loan/contracts/ABI_GENERATION.md similarity index 70% rename from loan/contracts/abi_generation.md rename to loan/contracts/ABI_GENERATION.md index cd67fe6d..5d8d063c 100644 --- a/loan/contracts/abi_generation.md +++ b/loan/contracts/ABI_GENERATION.md @@ -7,4 +7,4 @@ yarn ./loan/contracts/abi-creator.sh ``` -Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/asc-contracts` (same base as the bridge examples). Shared source-chain `TestERC20` lives under `shared/contracts/`. ABI regeneration: see [bridge/contracts/Contributor Notes.md](../../bridge/contracts/Contributor%20Notes.md) and `./shared/contracts/abi-creator.sh`. +Loan contracts import readability `ASCBase` and `EvmV1Decoder` from `@gluwa/asc-contracts` (same base as the bridge examples). Shared source-chain `TestERC20` lives under `shared/contracts/`. ABI regeneration: see [bridge/contracts/CONTRIBUTOR_NOTES.md](../../bridge/contracts/CONTRIBUTOR_NOTES.md) and `./shared/contracts/abi-creator.sh`. From 32aa966540ae400d9a7b2368120f30475941683f Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:57:27 -0600 Subject: [PATCH 16/31] Ignore bot-blocked npm and Google faucet URLs in lychee. MegaLinter lychee returns 403 for npm package pages and the Sepolia faucet link in CI; escape the npm pattern and add the faucet URL. --- .lycheeignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.lycheeignore b/.lycheeignore index 2384935f..8b7f2b83 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -28,4 +28,5 @@ https://github.com/gluwa/usc-contracts(.*) https://github.com/gluwa/asc-contracts(.*) https://docs.creditcoin.org/attestcoin-protocol(.*) https://docs.attestcoin.org(.*) -https://www.npmjs.com/package/@gluwa/asc-contracts(.*) +https://www\.npmjs\.com/package/@gluwa/asc-contracts +https://cloud\.google\.com/application/web3/faucet/ethereum/sepolia From b7ff9612846031ffb58dded474a89cdf867b6625 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:51:35 -0600 Subject: [PATCH 17/31] Overlap fixes from #103, #104, #105 --- VERIFICATION.md | 16 +- bridge/README.md | 4 +- bridge/bridge-offchain-worker/README.md | 2 +- bridge/contracts/CONTRIBUTOR_NOTES.md | 2 +- bridge/contracts/abi/ASCMinter.json | 247 ++++++++++----------- bridge/contracts/sol/ASCMinter.sol | 21 +- bridge/custom-contracts-bridging/README.md | 8 +- bridge/hello-bridge/README.md | 4 +- bridge/test/ASCMinterSecurity.t.sol | 121 ++++++++-- bridge/test/harness/ASCMinterHarness.sol | 16 +- loan/scripts/README.md | 4 +- shared/utils/index.ts | 4 +- 12 files changed, 260 insertions(+), 189 deletions(-) diff --git a/VERIFICATION.md b/VERIFICATION.md index 56c16677..86bcf6a4 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -14,12 +14,12 @@ yarn verify This runs: -| Step | Command | What it checks | -| ---------- | ---------------- | ---------------------------------------------------------------------------------------- | -| TypeScript | `yarn typecheck` | Tutorial scripts compile | -| Lint | `yarn eslint` | TS/JS style | -| Contracts | `yarn build` | `forge build` for bridge + loan | -| Unit tests | `forge test` | Loan source-contract binding; bridge burn-log parsing + emitter whitelist / query dedupe | +| Step | Command | What it checks | +| ---------- | ---------------- | ------------------------------------------------------------------------------------------- | +| TypeScript | `yarn typecheck` | Tutorial scripts compile | +| Lint | `yarn eslint` | TS/JS style | +| Contracts | `yarn build` | `forge build` for bridge + loan | +| Unit tests | `forge test` | Loan source-contract binding; bridge burn-log parsing + emitter registration / query dedupe | ### CI (`solidity.yml`) @@ -59,10 +59,10 @@ Tokens minted! Contract: 0x..., To: 0x..., Amount: 50000000000000000000, QueryId ## Manual end-to-end โ€” Custom bridge + loan - [ ] Deploy `EvmV1Decoder` once; set `EVM_V1_DECODER_LIBRARY_ADDRESS` -- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` (whitelists the Sepolia burn emitter) +- [ ] Deploy `ASCMinter`, wrapped token, `wrapOriginToken` (registers the Sepolia burn emitter) - [ ] Burn โ†’ `yarn custom_bridge:submit_query` โ†’ mint succeeds once - [ ] Second submit with same proof reverts (`Query already processed`) -- [ ] Proof of a burn from a non-whitelisted emitter reverts (`Emitter not whitelisted`) โ€” also covered by `forge test --root bridge` (`ASCMinterSecurity.t.sol`) +- [ ] Proof of a burn from an unregistered emitter reverts (`No wrapped token for emitter`) โ€” also covered by `forge test --root bridge` (`ASCMinterSecurity.t.sol`) - [ ] Deploy loan stack; `yarn loan_flow:register_source_contract` - [ ] Fund/repay on Sepolia; worker or manual proof updates loan status - [ ] Spoofed `LoanFunded` from unregistered contract cannot mark loan funded (covered by unit tests) diff --git a/bridge/README.md b/bridge/README.md index be19e81c..346c4d00 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -9,11 +9,11 @@ This example follows **[Attestcoin Readability โ€” Building a Bridge Minter (ยง6 | Piece | Role | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | **`ASCBase`** (from `@gluwa/asc-contracts`) | Shared readability base: verify via native precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. Same base as the loan example. | -| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens **only if the source-chain emitter is whitelisted** via `wrapOriginToken`. | +| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens **only if the source-chain emitter is registered** via `wrapOriginToken`. | | **`EvmV1Decoder`** (from `@gluwa/asc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/common/`). Not the messaging bridge. | | **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | -Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint** (mint requires the burn contract address to be a whitelisted emitter). +Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint** (mint requires the burn contract address to be registered via `wrapOriginToken`). Anyone with a valid proof can submit `execute`, but the ASC still rejects burns from emitters that were never registered; the offchain worker is optional UX automation. diff --git a/bridge/bridge-offchain-worker/README.md b/bridge/bridge-offchain-worker/README.md index d6eac548..007a9dd4 100644 --- a/bridge/bridge-offchain-worker/README.md +++ b/bridge/bridge-offchain-worker/README.md @@ -1,6 +1,6 @@ # Bridge Offchain Worker -Optional UX automation for the custom bridge: users only burn on Sepolia; the worker finishes the mint on Creditcoin so they do not run `yarn custom_bridge:submit_query` by hand. It is not required for security โ€” anyone with a valid proof can still call `ASCMinter.execute` manually, but the ASC still only mints for whitelisted source-chain emitters. +Optional UX automation for the custom bridge: users only burn on Sepolia; the worker finishes the mint on Creditcoin so they do not run `yarn custom_bridge:submit_query` by hand. It is not required for security โ€” anyone with a valid proof can still call `ASCMinter.execute` manually, but the ASC still only mints for source-chain emitters registered via `wrapOriginToken`. **Flow:** poll Sepolia for `TokensBurnedForBridging` โ†’ wait for attestation and fetch a USC proof from the Proof Builder โ†’ submit that proof via `ASCMinter.execute` on CC3 Testnet โ†’ log `TokensMinted` on Creditcoin (requires the burn contract to already be registered with `wrapOriginToken`). Same burn โ†’ proof โ†’ mint path as the tutorial, without a write-ability Relayer. diff --git a/bridge/contracts/CONTRIBUTOR_NOTES.md b/bridge/contracts/CONTRIBUTOR_NOTES.md index c1a3c4ad..16d1ae6b 100644 --- a/bridge/contracts/CONTRIBUTOR_NOTES.md +++ b/bridge/contracts/CONTRIBUTOR_NOTES.md @@ -15,7 +15,7 @@ For loan contracts: `./loan/contracts/abi-creator.sh`. Bridge example contracts (`ASCMinter`, tokens) inherit shared readability `ASCBase` from `@gluwa/asc-contracts` (`contracts/readability/ASCBase.sol`). `EvmV1Decoder` comes from the same package (`contracts/common/`). -`ASCMinter` mints only when the burn logโ€™s emitting address is in `whitelistedEmitters` (set by `wrapOriginToken`). Regenerate ABIs after changing that surface. +`ASCMinter` mints only when the burn logโ€™s emitting contract is registered via `wrapOriginToken` (`wrappedTokens`). Regenerate ABIs after changing that surface. ## Deploy troubleshooting diff --git a/bridge/contracts/abi/ASCMinter.json b/bridge/contracts/abi/ASCMinter.json index 3e40daa8..d8758284 100644 --- a/bridge/contracts/abi/ASCMinter.json +++ b/bridge/contracts/abi/ASCMinter.json @@ -1,230 +1,211 @@ [ { - "inputs": [ - { - "internalType": "uint8", - "name": "action", - "type": "uint8" - } - ], - "name": "InvalidAction", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "emitter", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "wrappedToken", - "type": "address" - } - ], - "name": "EmitterWhitelisted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "wrappedTokenAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "burntFrom", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "queryId", - "type": "bytes32" - } - ], - "name": "TokensMinted", - "type": "event" - }, - { - "inputs": [], + "type": "function", "name": "BURN_EVENT_SIGNATURE", + "inputs": [], "outputs": [ { - "internalType": "bytes32", "name": "", - "type": "bytes32" + "type": "bytes32", + "internalType": "bytes32" } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { - "inputs": [], + "type": "function", "name": "VERIFIER", + "inputs": [], "outputs": [ { - "internalType": "contract INativeQueryVerifier", "name": "", - "type": "address" + "type": "address", + "internalType": "contract INativeQueryVerifier" } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "execute", "inputs": [ { - "internalType": "uint8", "name": "action", - "type": "uint8" + "type": "uint8", + "internalType": "uint8" }, { - "internalType": "uint64", "name": "chainKey", - "type": "uint64" + "type": "uint64", + "internalType": "uint64" }, { - "internalType": "uint64", "name": "blockHeight", - "type": "uint64" + "type": "uint64", + "internalType": "uint64" }, { - "internalType": "bytes", "name": "encodedTransaction", - "type": "bytes" + "type": "bytes", + "internalType": "bytes" }, { - "internalType": "bytes32", "name": "merkleRoot", - "type": "bytes32" + "type": "bytes32", + "internalType": "bytes32" }, { + "name": "siblings", + "type": "tuple[]", + "internalType": "struct INativeQueryVerifier.MerkleProofEntry[]", "components": [ { - "internalType": "bytes32", "name": "hash", - "type": "bytes32" + "type": "bytes32", + "internalType": "bytes32" }, { - "internalType": "bool", "name": "isLeft", - "type": "bool" + "type": "bool", + "internalType": "bool" } - ], - "internalType": "struct INativeQueryVerifier.MerkleProofEntry[]", - "name": "siblings", - "type": "tuple[]" + ] }, { - "internalType": "bytes32", "name": "lowerEndpointDigest", - "type": "bytes32" + "type": "bytes32", + "internalType": "bytes32" }, { - "internalType": "bytes32[]", "name": "continuityRoots", - "type": "bytes32[]" + "type": "bytes32[]", + "internalType": "bytes32[]" } ], - "name": "execute", "outputs": [ { - "internalType": "bool", "name": "success", - "type": "bool" + "type": "bool", + "internalType": "bool" } ], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "processedQueries", "inputs": [ { - "internalType": "bytes32", "name": "", - "type": "bytes32" + "type": "bytes32", + "internalType": "bytes32" } ], - "name": "processedQueries", "outputs": [ { - "internalType": "bool", "name": "", - "type": "bool" + "type": "bool", + "internalType": "bool" } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "wrapOriginToken", + "inputs": [ + { + "name": "originToken", + "type": "address", + "internalType": "address" + }, + { + "name": "targetToken", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrappedTokens", "inputs": [ { - "internalType": "address", "name": "", - "type": "address" + "type": "address", + "internalType": "address" } ], - "name": "whitelistedEmitters", "outputs": [ { - "internalType": "bool", "name": "", - "type": "bool" + "type": "address", + "internalType": "address" } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { + "type": "event", + "name": "EmitterRegistered", "inputs": [ { - "internalType": "address", - "name": "originToken", - "type": "address" + "name": "emitter", + "type": "address", + "indexed": true, + "internalType": "address" }, { - "internalType": "address", - "name": "targetToken", - "type": "address" + "name": "wrappedToken", + "type": "address", + "indexed": true, + "internalType": "address" } ], - "name": "wrapOriginToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "anonymous": false }, { + "type": "event", + "name": "TokensMinted", "inputs": [ { - "internalType": "address", - "name": "", - "type": "address" + "name": "wrappedTokenAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "burntFrom", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "queryId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" } ], - "name": "wrappedTokens", - "outputs": [ + "anonymous": false + }, + { + "type": "error", + "name": "InvalidAction", + "inputs": [ { - "internalType": "address", - "name": "", - "type": "address" + "name": "action", + "type": "uint8", + "internalType": "uint8" } - ], - "stateMutability": "view", - "type": "function" + ] } ] diff --git a/bridge/contracts/sol/ASCMinter.sol b/bridge/contracts/sol/ASCMinter.sol index 5c915005..c23ddb17 100644 --- a/bridge/contracts/sol/ASCMinter.sol +++ b/bridge/contracts/sol/ASCMinter.sol @@ -8,13 +8,11 @@ import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.s /** * @title ASCMinter * @notice Simplified ASC that mints wrapped tokens after a proved source-chain burn. - * @dev Mints only when the burn log's emitting contract (source-chain token) is whitelisted - * via {wrapOriginToken}. Unregistered emitters are rejected even if the proof is valid. + * @dev Mints only when the burn log's emitting contract is registered via {wrapOriginToken}. */ contract ASCMinter is ASCBase { enum MinterActions { - None, // 0 โ€” holder - Mint // 1 + Mint // 0 โ€” matches pre-deployed stock minter used by hello-bridge } error InvalidAction(uint8 action); @@ -23,16 +21,13 @@ contract ASCMinter is ASCBase { 0x17dc4d6f69d484e59be774c29b47d2fa4c14af2e01df42fc5643ac968f4d427e; event TokensMinted(address indexed wrappedTokenAddress, address indexed burntFrom, uint256 amount, bytes32 indexed queryId); - event EmitterWhitelisted(address indexed emitter, address indexed wrappedToken); + event EmitterRegistered(address indexed emitter, address indexed wrappedToken); - /// @notice Source-chain burn contracts allowed to trigger mints (log `address` / emitter). - mapping(address => bool) public whitelistedEmitters; - - /// @notice Whitelisted emitter โ†’ wrapped ASCMintableToken on Creditcoin. + /// @notice Source-chain burn emitter โ†’ wrapped ASCMintableToken on Creditcoin. mapping(address => address) public wrappedTokens; /** - * @notice Whitelist a source-chain burn emitter and map it to a Creditcoin wrapped token. + * @notice Register a source-chain burn emitter and map it to a Creditcoin wrapped token. * @param originToken Source-chain ERC20 that emits `TokensBurnedForBridging` (the emitter). * @param targetToken Creditcoin ASCMintableToken that this ASC may mint. */ @@ -43,10 +38,9 @@ contract ASCMinter is ASCBase { require(ASCMintableToken(targetToken).owner() == msg.sender, "Target token must be owned by the caller"); require(ASCMintableToken(targetToken).hasRole(ASC_MINTER, address(this)), "Target token must be ASCMintableToken and support AccessControl"); - whitelistedEmitters[originToken] = true; wrappedTokens[originToken] = targetToken; - emit EmitterWhitelisted(originToken, targetToken); + emit EmitterRegistered(originToken, targetToken); } function _processAndEmitEvent(uint8 action, bytes32 queryId, bytes memory encodedTransaction) internal override { @@ -72,9 +66,6 @@ contract ASCMinter is ASCBase { (address emitter, address burntFrom, uint256 burntValue) = _processBurnLogs(burnLogs); - // Reject burns from contracts that were never whitelisted, even with a valid proof. - require(whitelistedEmitters[emitter], "Emitter not whitelisted"); - address wrappedTokenAddress = wrappedTokens[emitter]; require(wrappedTokenAddress != address(0), "No wrapped token for emitter"); diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index 128f006e..88a51f16 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -133,7 +133,7 @@ Reload: source .env ``` -### 3.4 Deploy wrapped token and whitelist the source emitter +### 3.4 Deploy wrapped token and register the source emitter Deploy the wrapped ERC20 owned by your minter: @@ -152,8 +152,8 @@ Update `.env`: ASC_CUSTOM_MINTABLE_TOKEN= ``` -Register (whitelist) the Sepolia burn contract as an allowed emitter and map it to the wrapped token. -`ASCMinter` mints **only** when the proved burn logโ€™s emitting address is on this whitelist โ€” a valid proof from any other contract is rejected: +Register the Sepolia burn contract as an allowed emitter and map it to the wrapped token. +`ASCMinter` mints **only** when the proved burn logโ€™s emitting address was registered with `wrapOriginToken` โ€” a valid proof from any other contract is rejected: ```bash cast send \ @@ -273,7 +273,7 @@ To experiment with customizing ASC logic, edit `bridge/contracts/sol/ASCMinter.s ## Conclusion -Congratulations! You've deployed your own simplified ASC bridge (`ASCMinter` + wrapped token), whitelisted the Sepolia burn emitter, and completed a trustless mint. +Congratulations! You've deployed your own simplified ASC bridge (`ASCMinter` + wrapped token), registered the Sepolia burn emitter, and completed a trustless mint. Next: [Loan Flow](../../loan/scripts/README.md) reuses your decoder library and extends the pattern with cross-chain loan state. diff --git a/bridge/hello-bridge/README.md b/bridge/hello-bridge/README.md index 0e366eb3..5e1874f9 100644 --- a/bridge/hello-bridge/README.md +++ b/bridge/hello-bridge/README.md @@ -7,7 +7,7 @@ This tutorial introduces you to one of the most common uses for a cross chain or bridging!** The flow is: 1. **Burn** tokens on the source chain (Sepolia). -2. **Submit proof + mint** on Creditcoin โ€” steps 2 and 3 are combined in `yarn hello_bridge:submit_query` (proof generation, attestation wait, and `ASCMinter.execute`). The pre-deployed ASC already has the Sepolia burner whitelisted as an emitter; only burns from that contract mint. +2. **Submit proof + mint** on Creditcoin โ€” steps 2 and 3 are combined in `yarn hello_bridge:submit_query` (proof generation, attestation wait, and `ASCMinter.execute`). The pre-deployed ASC already has the Sepolia burner registered via `wrapOriginToken`; only burns from that contract mint. ## Pre-deployed contracts (no deploy step) @@ -113,7 +113,7 @@ account address from [step 1.1]: ``` Note, that currently the faucet yields 100 test CTC every 24 hours. That is far more than these -tutorials need: submitting an oracle query costs only gas, on the order of 0.0002 CTC per query. +tutorials need: submitting an oracle query costs only gas, on the order of 0.000197 CTC per query. Now that your wallet is ready to make transactions on both networks, you will be needing a way to interact with it from the command line. diff --git a/bridge/test/ASCMinterSecurity.t.sol b/bridge/test/ASCMinterSecurity.t.sol index 085c0bcd..a747212f 100644 --- a/bridge/test/ASCMinterSecurity.t.sol +++ b/bridge/test/ASCMinterSecurity.t.sol @@ -4,51 +4,142 @@ pragma solidity ^0.8.23; import {Test} from "forge-std/Test.sol"; import {ASCMinterHarness} from "./harness/ASCMinterHarness.sol"; import {BridgeTestToken} from "../contracts/sol/BridgeTestToken.sol"; +import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; +import { + INativeQueryVerifier +} from "@gluwa/asc-contracts/contracts/write-ability/common/INativeQueryVerifier.sol"; -/// @notice Regression tests for bridge emitter whitelist and query replay dedupe (ASCBase). +/// @dev Stand-in for the Creditcoin native query verifier precompile (`0xFD2`) in unit tests. +contract MockNativeQueryVerifier { + function calculateTxIndex(INativeQueryVerifier.MerkleProof calldata) external pure returns (uint64) { + return 0; + } + + function verifyAndEmit( + uint64, + uint64, + bytes calldata, + INativeQueryVerifier.MerkleProof calldata, + INativeQueryVerifier.ContinuityProof calldata + ) external pure returns (bool) { + return false; + } +} + +/// @notice Regression tests for bridge emitter registration and query replay dedupe (ASCBase). contract ASCMinterSecurityTest is Test { ASCMinterHarness internal minter; + bytes32 internal constant BURN_EVENT_SIGNATURE = + 0x17dc4d6f69d484e59be774c29b47d2fa4c14af2e01df42fc5643ac968f4d427e; + + address internal constant VERIFIER_PRECOMPILE = + 0x0000000000000000000000000000000000000FD2; + address internal originEmitter = address(0xBEEF); address internal spoofedEmitter = address(0xBAD); + address internal burner = address(0xCAFE); function setUp() public { + MockNativeQueryVerifier mock = new MockNativeQueryVerifier(); + vm.etch(VERIFIER_PRECOMPILE, address(mock).code); + minter = new ASCMinterHarness(); } - function testEmitterWhitelist_rejectsUnregisteredEmitter() public { - vm.expectRevert("Emitter not whitelisted"); - minter.exposeRequireWhitelistedEmitter(originEmitter); + function testEmitterRegistration_rejectsUnregisteredEmitter() public { + bytes memory encoded = _encodedBurnTx(originEmitter, burner, 100); + + vm.expectRevert("No wrapped token for emitter"); + minter.exposeProcessMint(bytes32(0), encoded); } - function testEmitterWhitelist_acceptsAfterWrapOriginToken() public { + function testEmitterRegistration_acceptsAfterWrapOriginToken() public { BridgeTestToken wrapped = new BridgeTestToken(address(minter)); minter.wrapOriginToken(originEmitter, address(wrapped)); - assertTrue(minter.whitelistedEmitters(originEmitter)); assertEq(minter.wrappedTokens(originEmitter), address(wrapped)); - minter.exposeRequireWhitelistedEmitter(originEmitter); + bytes memory encoded = _encodedBurnTx(originEmitter, burner, 100); + minter.exposeProcessMint(bytes32(uint256(1)), encoded); + + assertEq(wrapped.balanceOf(burner), 100); } - function testEmitterWhitelist_rejectsSpoofedEmitterAfterWrap() public { + function testEmitterRegistration_rejectsSpoofedEmitterAfterWrap() public { BridgeTestToken wrapped = new BridgeTestToken(address(minter)); minter.wrapOriginToken(originEmitter, address(wrapped)); - vm.expectRevert("Emitter not whitelisted"); - minter.exposeRequireWhitelistedEmitter(spoofedEmitter); + bytes memory encoded = _encodedBurnTx(spoofedEmitter, burner, 100); + + vm.expectRevert("No wrapped token for emitter"); + minter.exposeProcessMint(bytes32(uint256(2)), encoded); } function testQueryDedupe_rejectsProcessedQueryId() public { - bytes32 queryId = keccak256("tutorial-replay-test"); + uint64 chainKey = 1; + uint64 blockHeight = 100; + bytes32 merkleRoot = bytes32(uint256(42)); + INativeQueryVerifier.MerkleProofEntry[] memory siblings = + new INativeQueryVerifier.MerkleProofEntry[](0); + + bytes32 queryId = minter.exposeComputeQueryId(chainKey, blockHeight, merkleRoot, siblings); minter.exposeMarkQueryProcessed(queryId); vm.expectRevert("Query already processed"); - minter.exposeRequireFreshQuery(queryId); + minter.execute( + 0, + chainKey, + blockHeight, + "", + merkleRoot, + siblings, + bytes32(0), + new bytes32[](0) + ); } - function testQueryDedupe_acceptsFreshQueryId() public view { - bytes32 queryId = keccak256("fresh-query"); - minter.exposeRequireFreshQuery(queryId); + function testQueryDedupe_acceptsFreshQueryId() public { + uint64 chainKey = 1; + uint64 blockHeight = 100; + bytes32 merkleRoot = bytes32(uint256(43)); + INativeQueryVerifier.MerkleProofEntry[] memory siblings = + new INativeQueryVerifier.MerkleProofEntry[](0); + + vm.expectRevert("Proof of inclusion verification failed"); + minter.execute( + 0, + chainKey, + blockHeight, + "", + merkleRoot, + siblings, + bytes32(0), + new bytes32[](0) + ); + } + + function _encodedBurnTx(address emitter, address from, uint256 amount) + internal + pure + returns (bytes memory encoded) + { + bytes32[] memory topics = new bytes32[](2); + topics[0] = BURN_EVENT_SIGNATURE; + topics[1] = bytes32(uint256(uint160(from))); + + EvmV1Decoder.LogEntryTuple[] memory logs = new EvmV1Decoder.LogEntryTuple[](1); + logs[0] = EvmV1Decoder.LogEntryTuple({ + address_: emitter, + topics: topics, + data: abi.encode(amount) + }); + + bytes[] memory chunks = new bytes[](3); + chunks[0] = abi.encode(uint64(0), uint64(21_000), address(0x1), false, address(0x2), uint256(0), bytes("")); + chunks[1] = abi.encode(uint128(1), uint256(27), bytes32(0), bytes32(0)); + chunks[2] = abi.encode(uint8(1), uint64(21_000), logs, bytes("")); + + encoded = abi.encode(uint8(0), chunks); } } diff --git a/bridge/test/harness/ASCMinterHarness.sol b/bridge/test/harness/ASCMinterHarness.sol index 969f0f52..7ee1c26b 100644 --- a/bridge/test/harness/ASCMinterHarness.sol +++ b/bridge/test/harness/ASCMinterHarness.sol @@ -3,6 +3,9 @@ pragma solidity ^0.8.23; import {ASCMinter} from "../../contracts/sol/ASCMinter.sol"; import {EvmV1Decoder} from "@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol"; +import { + INativeQueryVerifier +} from "@gluwa/asc-contracts/contracts/write-ability/common/INativeQueryVerifier.sol"; contract ASCMinterHarness is ASCMinter { function exposeProcessBurnLogs(EvmV1Decoder.LogEntry[] memory burnLogs) @@ -13,12 +16,17 @@ contract ASCMinterHarness is ASCMinter { return _processBurnLogs(burnLogs); } - function exposeRequireWhitelistedEmitter(address emitter) external view { - require(whitelistedEmitters[emitter], "Emitter not whitelisted"); + function exposeProcessMint(bytes32 queryId, bytes memory encodedTransaction) external { + _processMint(queryId, encodedTransaction); } - function exposeRequireFreshQuery(bytes32 queryId) external view { - require(!processedQueries[queryId], "Query already processed"); + function exposeComputeQueryId( + uint64 chainKey, + uint64 blockHeight, + bytes32 merkleRoot, + INativeQueryVerifier.MerkleProofEntry[] calldata siblings + ) external view returns (bytes32) { + return _computeQueryId(chainKey, blockHeight, merkleRoot, siblings); } function exposeMarkQueryProcessed(bytes32 queryId) external { diff --git a/loan/scripts/README.md b/loan/scripts/README.md index efe4f5d0..4443dd5f 100644 --- a/loan/scripts/README.md +++ b/loan/scripts/README.md @@ -320,7 +320,7 @@ Which will output something like this: ```sh Loan Details: - Loan ID: 1 + Loan ID: 5 From: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 To: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 With Token: 0x5FbDB2315678afecb367f032d93F642f64180aa3 # betterleaks:allow @@ -422,7 +422,7 @@ Marked loan 5 as repaid on Creditcoin, tx hash: 0xa1c22b43c37e51734d3d388fcd583a Loan 5 has been partially repaid on Creditcoin. Amount repaid: 1000 ``` -Wait a minute... what do you mean partially repaid!? The loan was for 1000 tokens, this is not fair! +Wait a minute... what do you mean partially repaid!? The expected repayment was 1050 tokens, this is not fair! Hmm... oh! The interest! I forgot about that! ```sh diff --git a/shared/utils/index.ts b/shared/utils/index.ts index 9968c3ae..b7c2e8d0 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts @@ -143,7 +143,7 @@ export async function computeGasLimitForMinter( proofData: proofProvider.ContinuityResponse, signerAddress: string ): Promise { - const action = 1; // Mint action (see MinterActions in ASCMinter: None=0, Mint=1) + const action = 0; // Mint action (see MinterActions in ASCMinter: Mint=0) const chainKey = proofData.chainKey; const height = proofData.headerNumber; const encodedTransaction = proofData.txBytes; @@ -250,7 +250,7 @@ export async function submitProofToMinter( proofData: proofProvider.ContinuityResponse, gasLimit: bigint ): Promise { - const action = 1; // Mint action (see MinterActions in ASCMinter: None=0, Mint=1) + const action = 0; // Mint action (see MinterActions in ASCMinter: Mint=0) const chainKey = proofData.chainKey; const height = proofData.headerNumber; const encodedTransaction = proofData.txBytes; From c6720ed5d39193725fbd84c011ab004ab1227e0a Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:55:40 -0600 Subject: [PATCH 18/31] Regenerate ASCMinter ABI with solc output format expected by CI. Prettier had reordered ABI JSON keys; restore abi-creator.sh format, enforce LF line endings for ABI JSON, and exclude generated ABI dirs from Prettier. --- .gitattributes | 3 + .prettierignore | 5 + bridge/contracts/abi/ASCMinter.json | 238 ++++++++++++++-------------- 3 files changed, 127 insertions(+), 119 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..62aa89a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +bridge/contracts/abi/*.json text eol=lf +loan/contracts/abi/*.json text eol=lf +shared/contracts/abi/*.json text eol=lf diff --git a/.prettierignore b/.prettierignore index a10ebb18..351bc7cf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,3 +15,8 @@ vendor # TS Config tsconfig.json + +# ABI files are generated by solc+jq (abi-creator.sh); do not reformat with Prettier. +bridge/contracts/abi +loan/contracts/abi +shared/contracts/abi diff --git a/bridge/contracts/abi/ASCMinter.json b/bridge/contracts/abi/ASCMinter.json index d8758284..a6c70bd6 100644 --- a/bridge/contracts/abi/ASCMinter.json +++ b/bridge/contracts/abi/ASCMinter.json @@ -1,211 +1,211 @@ [ { - "type": "function", - "name": "BURN_EVENT_SIGNATURE", + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidAction", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "emitter", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "EmitterRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "wrappedTokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "burntFrom", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "queryId", + "type": "bytes32" + } + ], + "name": "TokensMinted", + "type": "event" + }, + { "inputs": [], + "name": "BURN_EVENT_SIGNATURE", "outputs": [ { + "internalType": "bytes32", "name": "", - "type": "bytes32", - "internalType": "bytes32" + "type": "bytes32" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "VERIFIER", "inputs": [], + "name": "VERIFIER", "outputs": [ { + "internalType": "contract INativeQueryVerifier", "name": "", - "type": "address", - "internalType": "contract INativeQueryVerifier" + "type": "address" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "execute", "inputs": [ { + "internalType": "uint8", "name": "action", - "type": "uint8", - "internalType": "uint8" + "type": "uint8" }, { + "internalType": "uint64", "name": "chainKey", - "type": "uint64", - "internalType": "uint64" + "type": "uint64" }, { + "internalType": "uint64", "name": "blockHeight", - "type": "uint64", - "internalType": "uint64" + "type": "uint64" }, { + "internalType": "bytes", "name": "encodedTransaction", - "type": "bytes", - "internalType": "bytes" + "type": "bytes" }, { + "internalType": "bytes32", "name": "merkleRoot", - "type": "bytes32", - "internalType": "bytes32" + "type": "bytes32" }, { - "name": "siblings", - "type": "tuple[]", - "internalType": "struct INativeQueryVerifier.MerkleProofEntry[]", "components": [ { + "internalType": "bytes32", "name": "hash", - "type": "bytes32", - "internalType": "bytes32" + "type": "bytes32" }, { + "internalType": "bool", "name": "isLeft", - "type": "bool", - "internalType": "bool" + "type": "bool" } - ] + ], + "internalType": "struct INativeQueryVerifier.MerkleProofEntry[]", + "name": "siblings", + "type": "tuple[]" }, { + "internalType": "bytes32", "name": "lowerEndpointDigest", - "type": "bytes32", - "internalType": "bytes32" + "type": "bytes32" }, { + "internalType": "bytes32[]", "name": "continuityRoots", - "type": "bytes32[]", - "internalType": "bytes32[]" + "type": "bytes32[]" } ], + "name": "execute", "outputs": [ { + "internalType": "bool", "name": "success", - "type": "bool", - "internalType": "bool" + "type": "bool" } ], - "stateMutability": "nonpayable" + "stateMutability": "nonpayable", + "type": "function" }, { - "type": "function", - "name": "processedQueries", "inputs": [ { + "internalType": "bytes32", "name": "", - "type": "bytes32", - "internalType": "bytes32" + "type": "bytes32" } ], + "name": "processedQueries", "outputs": [ { + "internalType": "bool", "name": "", - "type": "bool", - "internalType": "bool" + "type": "bool" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "wrapOriginToken", "inputs": [ { + "internalType": "address", "name": "originToken", - "type": "address", - "internalType": "address" + "type": "address" }, { + "internalType": "address", "name": "targetToken", - "type": "address", - "internalType": "address" + "type": "address" } ], + "name": "wrapOriginToken", "outputs": [], - "stateMutability": "nonpayable" + "stateMutability": "nonpayable", + "type": "function" }, { - "type": "function", - "name": "wrappedTokens", "inputs": [ { + "internalType": "address", "name": "", - "type": "address", - "internalType": "address" + "type": "address" } ], + "name": "wrappedTokens", "outputs": [ { + "internalType": "address", "name": "", - "type": "address", - "internalType": "address" + "type": "address" } ], - "stateMutability": "view" - }, - { - "type": "event", - "name": "EmitterRegistered", - "inputs": [ - { - "name": "emitter", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "wrappedToken", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "TokensMinted", - "inputs": [ - { - "name": "wrappedTokenAddress", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "burntFrom", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "queryId", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "InvalidAction", - "inputs": [ - { - "name": "action", - "type": "uint8", - "internalType": "uint8" - } - ] + "stateMutability": "view", + "type": "function" } ] From 5c88f09a98a9511ccbd344db56efd36559505ee7 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:26:30 -0600 Subject: [PATCH 19/31] Fix CI E2E race and lychee excludes for bot-blocked URLs. Wait for source-chain burn receipts before proof generation, and restore anchored lychee patterns for npm, docs.attestcoin.org, and the Google faucet. --- .lycheeignore | 6 +++--- .mega-linter.yml | 3 +++ shared/utils/index.ts | 14 +++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.lycheeignore b/.lycheeignore index 8b7f2b83..bc2fcf08 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -27,6 +27,6 @@ https://blockchain.googleapis.com/v1/projects/(.*) https://github.com/gluwa/usc-contracts(.*) https://github.com/gluwa/asc-contracts(.*) https://docs.creditcoin.org/attestcoin-protocol(.*) -https://docs.attestcoin.org(.*) -https://www\.npmjs\.com/package/@gluwa/asc-contracts -https://cloud\.google\.com/application/web3/faucet/ethereum/sepolia +^https://www\.npmjs\.com/package/@gluwa/asc-contracts(.*) +^https://cloud\.google\.com/application/web3/faucet/ethereum/sepolia(.*) +^https://docs\.attestcoin\.org(.*) diff --git a/.mega-linter.yml b/.mega-linter.yml index 43faf870..3323ae04 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -30,3 +30,6 @@ DISABLE_LINTERS: - MARKDOWN_MARKDOWN_TABLE_FORMATTER SHOW_ELAPSED_TIME: true + +# Bot-blocked or flaky doc/npm links (also listed in .lycheeignore). +SPELL_LYCHEE_ARGUMENTS: "--exclude '^https://www\\.npmjs\\.com/package/@gluwa/asc-contracts' --exclude '^https://cloud\\.google\\.com/application/web3/faucet/ethereum/sepolia' --exclude '^https://docs\\.attestcoin\\.org'" diff --git a/shared/utils/index.ts b/shared/utils/index.ts index b7c2e8d0..c8611785 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts @@ -21,18 +21,14 @@ export async function generateProofFor( creditcoinRpc: JsonRpcApiProvider, sourceChainRpc: JsonRpcApiProvider ): Promise { - // First, we need to ensure that the transaction exists on the source chain - const transaction = await sourceChainRpc.getTransaction(txHash); - if (!transaction) { - throw new Error(`Transaction ${txHash} does not exist on source chain`); - } - - // Next, we need to ensure that the block is mined - const blockNumber = transaction.blockNumber; - if (!blockNumber) { + // Wait until the burn tx is mined (CI submits immediately after cast send). + console.log(`Waiting for transaction ${txHash} to be mined on source chain...`); + const receipt = await sourceChainRpc.waitForTransaction(txHash, 1, 120_000); + if (!receipt || receipt.blockNumber == null) { throw new Error(`Transaction ${txHash} is not yet mined on source chain`); } + const blockNumber = receipt.blockNumber; console.log(`Transaction ${txHash} found in block ${blockNumber}`); // Now that we have the block number, we can listen for the required attestation From a2e364ba12014b27e4427b05ebfff85b58940eea Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:33:37 -0600 Subject: [PATCH 20/31] Fix MegaLinter lychee failures caused by exclude patterns in YAML. Remove SPELL_LYCHEE_ARGUMENTS (lychee was parsing exclude regex as URLs in .mega-linter.yml) and rely on .lycheeignore for npm, docs.attestcoin.org, faucet, and gitignored .env file links. --- .lycheeignore | 7 ++++--- .mega-linter.yml | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.lycheeignore b/.lycheeignore index bc2fcf08..48675dbb 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -2,6 +2,7 @@ http://localhost(.*) ws|wss://(.*) bzz-raw://(.*) +file://.*\.env rpc\.(.*)\.creditcoin\.network/ staticsitellvmhtml.z13.web.core.windows.net thedoctor0/zip-release@(.*) @@ -27,6 +28,6 @@ https://blockchain.googleapis.com/v1/projects/(.*) https://github.com/gluwa/usc-contracts(.*) https://github.com/gluwa/asc-contracts(.*) https://docs.creditcoin.org/attestcoin-protocol(.*) -^https://www\.npmjs\.com/package/@gluwa/asc-contracts(.*) -^https://cloud\.google\.com/application/web3/faucet/ethereum/sepolia(.*) -^https://docs\.attestcoin\.org(.*) +https://docs\.attestcoin\.org +https://www\.npmjs\.com/package/@gluwa/asc-contracts +https://cloud\.google\.com/application/web3/faucet/ethereum/sepolia diff --git a/.mega-linter.yml b/.mega-linter.yml index 3323ae04..43faf870 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -30,6 +30,3 @@ DISABLE_LINTERS: - MARKDOWN_MARKDOWN_TABLE_FORMATTER SHOW_ELAPSED_TIME: true - -# Bot-blocked or flaky doc/npm links (also listed in .lycheeignore). -SPELL_LYCHEE_ARGUMENTS: "--exclude '^https://www\\.npmjs\\.com/package/@gluwa/asc-contracts' --exclude '^https://cloud\\.google\\.com/application/web3/faucet/ethereum/sepolia' --exclude '^https://docs\\.attestcoin\\.org'" From 887ce52824baee12c67c4d99b6ec0f072b2a40b1 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 13:31:29 -0600 Subject: [PATCH 21/31] Enable loan-flow in examples-devnet CI and install forge-std. Remove the if: false gate so loan-flow runs after the other devnet examples, and clone forge-std in the reusable workflow for loan contract deploys. --- .github/workflows/examples-devnet.yml | 4 +--- .github/workflows/loan-flow.yml | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/examples-devnet.yml b/.github/workflows/examples-devnet.yml index 0c295714..a1eab1af 100644 --- a/.github/workflows/examples-devnet.yml +++ b/.github/workflows/examples-devnet.yml @@ -43,9 +43,7 @@ jobs: secrets: inherit loan-flow: - if: false - # chain examples b/c they use the same wallet and we get - # replacement transaction underpriced and/or nonce errors + # Run after other devnet examples (shared wallet) to avoid nonce collisions. needs: bridge-offchain-worker uses: ./.github/workflows/loan-flow.yml with: diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 4c7919c8..4d8c3773 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -166,6 +166,9 @@ jobs: - name: Install dependencies run: yarn install --ignore-engines + - name: Install forge-std + run: git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std + - name: Setup id: setup env: From cd097c9b2b74f9643916cbec8cf27436ee3b3b3c Mon Sep 17 00:00:00 2001 From: BradleyOlson64 Date: Tue, 1 Sep 2026 12:35:14 -0700 Subject: [PATCH 22/31] First round changes --- .env.devnet.example | 38 ------------------ .env.example | 38 ------------------ README.md | 50 ++---------------------- bridge/.env.example | 3 +- bridge/README.md | 50 +----------------------- bridge/bridge-offchain-worker/worker.ts | 4 +- bridge/hello-bridge/README.md | 28 ++++--------- loan/.env.example | 3 +- loan/scripts/authorize.ts | 4 +- loan/scripts/fund_loan.ts | 4 +- loan/scripts/inspect.ts | 4 +- loan/scripts/register.ts | 4 +- loan/scripts/register_source_contract.ts | 4 +- loan/scripts/repay_loan.ts | 4 +- loan/scripts/worker.ts | 4 +- shared/check_setup.ts | 49 ++++++++++------------- shared/env.ts | 41 +++++++++++++++++++ shared/submit_bridge_query.ts | 4 +- 18 files changed, 94 insertions(+), 242 deletions(-) delete mode 100644 .env.devnet.example delete mode 100644 .env.example create mode 100644 shared/env.ts diff --git a/.env.devnet.example b/.env.devnet.example deleted file mode 100644 index 7f960bd5..00000000 --- a/.env.devnet.example +++ /dev/null @@ -1,38 +0,0 @@ -## These values should not be changed during the course of the tutorials ## -# Source chain key for Sepolia on CC3 Devnet (different from its chain ID, which is 11155111) -# Chain key is the source chain identifier for all ASC interactions on Creditcoin -# You can obtain the source chain key through the SupportedChains smart contract deployed on CC3 Devnet -SOURCE_CHAIN_KEY=1 -# Proof Builder URL for CC3 Devnet -PROOF_BUILDER_URL="https://prover.cc3-devnet.creditcoin.network/" -# ERC20 burner contract on Sepolia -SOURCE_CHAIN_CONTRACT_ADDRESS="0x53311D909aC66Fb2a37A90466991B17BFaFB6EBa" -# ASC Minter contract on CC3 Devnet -ASC_MINTER_CONTRACT_ADDRESS="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" -# ASC Mintable Token contract on CC3 Devnet -ASC_MINTABLE_TOKEN="0x9d960f72121189de0ca0bcE0133359a1285a4a7C" # betterleaks:allow -# Creditcoin RPC URL for CC3 Devnet -CREDITCOIN_RPC_URL="https://rpc.cc3-devnet.creditcoin.network" - -## These values will get filled as the tutorials are performed ## -# Address of the Sepolia RPC URL e.g: https://sepolia.infura.io/v3/ -SOURCE_CHAIN_RPC_URL="" -# Address of your custom ERC20 contract for burning on Sepolia -SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" -# Address of your custom ASC Minter contract on CC3 Devnet -ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" -# Your custom ASC Mintable Token contract on CC3 Devnet -ASC_CUSTOM_MINTABLE_TOKEN="" -# Your private key on CC3 Devnet and Sepolia -CREDITCOIN_WALLET_PRIVATE_KEY="" - -# Address of the custom ASC Loan manager contract on CC3 Devnet -ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" -# Address of the custom Loan contract on Sepolia -SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" -# Addres of source chain ERC20 contract from which funding will happen -SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" -# Private key of the account to use as lender in the loan flow -LENDER_WALLET_PRIVATE_KEY="" -# Private key of the account to use as borrower in the loan flow -BORROWER_WALLET_PRIVATE_KEY="" diff --git a/.env.example b/.env.example deleted file mode 100644 index b4b62cde..00000000 --- a/.env.example +++ /dev/null @@ -1,38 +0,0 @@ -## These values should not be changed during the course of the tutorials ## -# Source chain key for Sepolia on CC3 Testnet (different from its chain ID, which is 11155111) -# Chain key is the source chain identifier for all ASC interactions on Creditcoin -# You can obtain the source chain key through the SupportedChains smart contract deployed on CC3 Testnet -SOURCE_CHAIN_KEY=1 -# Proof Builder URL for CC3 Testnet -PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" -# ERC20 burner contract on Sepolia -SOURCE_CHAIN_CONTRACT_ADDRESS="0x0F24FD9e0524BA53d3f0A4A40350Adf5370b4A53" -# ASC Minter contract on CC3 Testnet -ASC_MINTER_CONTRACT_ADDRESS="0x2Be9B8640ED32815d3B9e8C92AbcD3F15F07396f" -# ASC Mintable Token contract on CC3 Testnet -ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" # betterleaks:allow -# Creditcoin RPC URL for CC3 Testnet -CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" - -## These values will get filled as the tutorials are performed ## -# Address of the Sepolia RPC URL e.g: https://sepolia.infura.io/v3/ -SOURCE_CHAIN_RPC_URL="" -# Address of your custom ERC20 contract for burning on Sepolia -SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" -# Address of your custom ASC Minter contract on CC3 Testnet -ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" -# Address of your custom MintableToken -ASC_CUSTOM_MINTABLE_TOKEN="" -# Your private key on CC3 Testnet and Sepolia -CREDITCOIN_WALLET_PRIVATE_KEY="" - -# Address of the custom ASC Loan manager contract on CC3 Testnet -ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" -# Address of the custom Loan contract on Sepolia -SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" -# Addres of source chain ERC20 contract from which funding will happen -SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" -# Private key of the account to use as lender in the loan flow -LENDER_WALLET_PRIVATE_KEY="" -# Private key of the account to use as borrower in the loan flow -BORROWER_WALLET_PRIVATE_KEY="" diff --git a/README.md b/README.md index 457e832f..f7b54742 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,8 @@ -# ๐ŸŒ‰ ASC Testnet Bridge Examples ๐ŸŒ‰ +# ๐ŸŒ‰ Attestcoin Protocol Examples ๐ŸŒ‰ -This repository is a starting point for exploring Creditcoin cross-chain **readability** (Attestcoin Smart Contracts). The **bridge** tutorials use a **simplified ASC bridge** โ€” local `ASCMinter` on shared package `ASCBase`, direct `execute` calls โ€” **not** the full write-ability production stack (Outbox / Relayer / Inbox). See [bridge/README.md](./bridge/README.md). +This repository is a starting point for exploring cross-chain inter-operability using the Attestcoin protocol. Today, it contains several examples covering Attestcoin readability. Coming soon: examples using Attestcoin writability! -`@gluwa/asc-contracts` is installed from npm ([`@gluwa/asc-contracts`](https://www.npmjs.com/package/@gluwa/asc-contracts)). - -Learn how to use the Creditcoin Decentralized Oracle through guided tutorials where you set up and interact with your own decentralized bridge and loan examples. - -## Repository layout - -```text -usc-testnet-bridge-examples/ -โ”œโ”€โ”€ bridge/ # Simplified ASC bridge tutorials + ASCMinter (ASCBase from @gluwa/asc-contracts) -โ”‚ โ”œโ”€โ”€ contracts/ # Example ASC apps; ASCBase + EvmV1Decoder from @gluwa/asc-contracts -โ”‚ โ”œโ”€โ”€ hello-bridge/ -โ”‚ โ”œโ”€โ”€ custom-contracts-bridging/ -โ”‚ โ””โ”€โ”€ bridge-offchain-worker/ -โ”œโ”€โ”€ loan/ # Cross-chain loan tutorial + loan contracts -โ”‚ โ”œโ”€โ”€ contracts/ # ASCLoanManager, AuxiliaryLoanContract, โ€ฆ -โ”‚ โ””โ”€โ”€ scripts/ # Loan flow CLI scripts -โ”œโ”€โ”€ shared/ -โ”‚ โ”œโ”€โ”€ contracts/ # TestERC20 (shared Sepolia burner for bridge + loan) -โ”‚ โ””โ”€โ”€ utils/ # Proof builder + submit helpers shared by both examples -``` - -Package overview: [bridge/README.md](./bridge/README.md) (simplified bridge model and what is out of scope). +By running through the tutorials in this repository, you will set up and interact with your own decentralized bridge and loan examples. ## Before you start @@ -44,29 +23,6 @@ Set up Foundry: foundryup --version v1.2.3 ``` -Copy and fill in environment files for the examples you plan to run. `.env` holds your wallet -private key, so it is git-ignored and never committed โ€” `.env.example` is the tracked copy, which -carries only public network defaults: - -- Bridge tutorials: `bridge/.env.example` โ†’ `bridge/.env` (or root `.env`) -- Loan tutorial: `loan/.env.example` โ†’ `loan/.env` (or root `.env`) - -> [!CAUTION] -> Keep your private key in `.env` only. Never put it in `.env.example`, which is tracked by git. - -Then source your env file and verify connectivity: - -```bash -source .env -yarn utils:check_setup hello # Hello Bridge -yarn utils:check_setup bridge # after custom-contracts deploy -yarn utils:check_setup loan # after loan stack deploy -``` - -Deploy `EvmV1Decoder` once per Creditcoin network; save `EVM_V1_DECODER_LIBRARY_ADDRESS` in `.env` and reuse for the loan tutorial. - -Full verification checklist: [VERIFICATION.md](./VERIFICATION.md). - ## Tutorials We recommend going through them in this order: diff --git a/bridge/.env.example b/bridge/.env.example index 2310a00f..0396a48c 100644 --- a/bridge/.env.example +++ b/bridge/.env.example @@ -9,6 +9,7 @@ SOURCE_CHAIN_CONTRACT_ADDRESS="0x0F24FD9e0524BA53d3f0A4A40350Adf5370b4A53" ASC_MINTER_CONTRACT_ADDRESS="0x2Be9B8640ED32815d3B9e8C92AbcD3F15F07396f" ASC_MINTABLE_TOKEN="0x914Cf96BF28b7b4921db27b264ecEd71aC91134E" # betterleaks:allow CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" +EVM_V1_DECODER_LIBRARY_ADDRESS="0x04B9ae8562D8Cc5bbbBbBB759080dDC30B56D18B" ## User-provided SOURCE_CHAIN_RPC_URL="" @@ -18,5 +19,3 @@ CREDITCOIN_WALLET_PRIVATE_KEY="" SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS="" ASC_CUSTOM_MINTER_CONTRACT_ADDRESS="" ASC_CUSTOM_MINTABLE_TOKEN="" -# Reuse across bridge + loan deploys on the same Creditcoin network -EVM_V1_DECODER_LIBRARY_ADDRESS="" diff --git a/bridge/README.md b/bridge/README.md index 346c4d00..b005b801 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -1,57 +1,11 @@ # Bridge examples (simplified ASC bridge) -These tutorials implement a **simplified readability bridge** โ€” not the production write-ability stack (Outbox, Relayer, Inbox, liquidity operators, etc.) published as [`@gluwa/asc-contracts`](https://www.npmjs.com/package/@gluwa/asc-contracts). +These tutorials implement a simplified one-way bridge using Attestcoin readability. Tutorials implementing a full two-way bridge using both readability and writability will be added soon, after the writability core code passes 3rd party audit. -This example follows **[Attestcoin Readability โ€” Building a Bridge Minter (ยง6.4)](https://docs.attestcoin.org/attestcoin-protocol/attestcoin-readability)** (inherit `ASCBase`, prove a source-chain burn, mint wrapped tokens on Creditcoin). - -## What this example uses - -| Piece | Role | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | -| **`ASCBase`** (from `@gluwa/asc-contracts`) | Shared readability base: verify via native precompile (`0xFD2`), dedupe by `queryId`, delegate to app logic. Same base as the loan example. | -| **`ASCMinter`** (local) | Decode a `TokensBurnedForBridging` log and mint wrapped tokens **only if the source-chain emitter is registered** via `wrapOriginToken`. | -| **`EvmV1Decoder`** (from `@gluwa/asc-contracts`) | Shared library for decoding proved EVM receipts/logs (deploy via `contracts/common/`). Not the messaging bridge. | -| **Tutorial scripts** | Build proofs and call `ASCMinter.execute(...)` directly. No relayer contract. | - -Flow: **burn on source chain โ†’ wait for attestation โ†’ fetch proof โ†’ `execute` on ASC โ†’ mint** (mint requires the burn contract address to be registered via `wrapOriginToken`). - -Anyone with a valid proof can submit `execute`, but the ASC still rejects burns from emitters that were never registered; the offchain worker is optional UX automation. - -## What this example does not use - -- Outbox / Relayer / Inbox messaging -- `ASCBridgeLiquidityOperator`, `ClientBridgeLiquidityOperator`, or other write-ability bridge contracts -- Trusted relayers or operator-gated mint paths - -For the production bridge architecture, see [`@gluwa/asc-contracts`](https://www.npmjs.com/package/@gluwa/asc-contracts) and Creditcoin's write-ability documentation. - -## Layout - -``` -bridge/ -โ”œโ”€โ”€ contracts/sol/ ASCMinter, MintableToken, BridgeTestToken (ASCBase from package) -โ”œโ”€โ”€ hello-bridge/ Pre-deployed ASC + burner (minimal setup) -โ”œโ”€โ”€ custom-contracts-bridging/ Deploy your own ASC + wrapped token -โ””โ”€โ”€ bridge-offchain-worker/ Automate proof submission after burns -``` - -## Tutorials (recommended order) +## Tutorials in This Directory (recommended order) 1. [Hello Bridge](./hello-bridge/README.md) 2. [Custom Contracts Bridging](./custom-contracts-bridging/README.md) 3. [Bridge Offchain Worker](./bridge-offchain-worker/README.md) Deployment troubleshooting: [contracts/CONTRIBUTOR_NOTES.md](./contracts/CONTRIBUTOR_NOTES.md). - -## Before you run tutorials - -```sh -yarn utils:check_setup hello # after configuring .env for Hello Bridge -yarn utils:check_setup bridge # after custom-contracts deploy -``` - -Deploy `EvmV1Decoder` **once** per Creditcoin network; set `EVM_V1_DECODER_LIBRARY_ADDRESS` in `.env` and reuse for loan deploys. - -## Environment - -Network and RPC settings live in the repository root [`.env`](../.env). Bridge-specific variable names are listed in [`.env.example`](./.env.example) as a convenience split. diff --git a/bridge/bridge-offchain-worker/worker.ts b/bridge/bridge-offchain-worker/worker.ts index ea63ce33..688b2aa8 100644 --- a/bridge/bridge-offchain-worker/worker.ts +++ b/bridge/bridge-offchain-worker/worker.ts @@ -1,4 +1,3 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import burnerAbi from '../../shared/contracts/abi/TestERC20.json'; @@ -13,8 +12,9 @@ import { isValidContractAddress, isValidPrivateKey, } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('bridge'); // Graceful shutdown flag let isShuttingDown = false; diff --git a/bridge/hello-bridge/README.md b/bridge/hello-bridge/README.md index 5e1874f9..4ce7c963 100644 --- a/bridge/hello-bridge/README.md +++ b/bridge/hello-bridge/README.md @@ -1,7 +1,7 @@ # Hello Bridge > [!NOTE] -> This tutorial uses the **simplified ASC bridge** (`ASCMinter` on package `ASCBase`), not the write-ability Outbox/Relayer stack. Overview: [bridge/README.md](../README.md). +> This tutorial implements a simplified one-way bridge using Attestcoin readability. Tutorials implementing a full two-way bridge using both readability and writability will be added soon, after the writability core code passes 3rd party audit. This tutorial introduces you to one of the most common uses for a cross chain oracle, **cross chain bridging!** The flow is: @@ -11,20 +11,14 @@ bridging!** The flow is: ## Pre-deployed contracts (no deploy step) -Hello Bridge uses **shared tutorial contracts** already deployed on CC3 Testnet and Sepolia. The repository root [`.env`](../../.env) includes pre-filled values for: +Hello Bridge uses **shared tutorial contracts** already deployed on CC3 Testnet and Sepolia. The /bridge [`.env.example`](../.env.example) includes pre-filled values for several contracts. Just copy the example env using: -| Variable | Purpose | -| ------------------------------- | --------------------------------------- | -| `SOURCE_CHAIN_KEY` | Sepolia chain key on CC3 Testnet (`1`) | -| `PROOF_BUILDER_URL` | Proof builder for CC3 Testnet | -| `CREDITCOIN_RPC_URL` | CC3 Testnet RPC | -| `SOURCE_CHAIN_CONTRACT_ADDRESS` | Pre-deployed Sepolia burner ERC20 | -| `ASC_MINTER_CONTRACT_ADDRESS` | Pre-deployed `ASCMinter` on CC3 Testnet | -| `ASC_MINTABLE_TOKEN` | Pre-deployed wrapped token | - -Copy those lines into your own `.env` if starting fresh. Variable names are also listed in [bridge/.env.example](../.env.example). +```sh +cd bridge +cp .env.example .env.test +``` -You only need to add **`CREDITCOIN_WALLET_PRIVATE_KEY`** and **`SOURCE_CHAIN_RPC_URL`** (Infura Sepolia), then run: +Then you only need to add **`CREDITCOIN_WALLET_PRIVATE_KEY`** and **`SOURCE_CHAIN_RPC_URL`** (Infura Sepolia), then run: ```sh source .env @@ -75,14 +69,6 @@ Address: 0xBE7959cA1b19e159D8C0649860793dDcd125a2D5 Private key: 0xb9c179ed56514accb60c23a862194fa2a6db8bdeb815d16e2c21aa4d7dc2845d # betterleaks:allow ``` -Save this private key on the `.env` file in the root of the repository. If you have not created -it yet, copy it from the tracked template first โ€” `.env` is git-ignored, so your key stays out -of version control: - -```bash -cp .env.example .env -``` - ```env CREDITCOIN_WALLET_PRIVATE_KEY= ``` diff --git a/loan/.env.example b/loan/.env.example index c767359d..3e69c066 100644 --- a/loan/.env.example +++ b/loan/.env.example @@ -4,13 +4,12 @@ SOURCE_CHAIN_KEY=1 PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" +EVM_V1_DECODER_LIBRARY_ADDRESS="0x04B9ae8562D8Cc5bbbBbBB759080dDC30B56D18B" SOURCE_CHAIN_RPC_URL="" ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" -# Reuse from bridge tutorial if already deployed -EVM_V1_DECODER_LIBRARY_ADDRESS="" LENDER_WALLET_PRIVATE_KEY="" BORROWER_WALLET_PRIVATE_KEY="" diff --git a/loan/scripts/authorize.ts b/loan/scripts/authorize.ts index 0523f5c4..eecaf59a 100644 --- a/loan/scripts/authorize.ts +++ b/loan/scripts/authorize.ts @@ -1,10 +1,10 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); const main = async () => { const args = process.argv.slice(2); diff --git a/loan/scripts/fund_loan.ts b/loan/scripts/fund_loan.ts index 7d18e867..7b70dfea 100644 --- a/loan/scripts/fund_loan.ts +++ b/loan/scripts/fund_loan.ts @@ -1,11 +1,11 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import ERC20Abi from '../../shared/contracts/abi/TestERC20.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); const main = async () => { const args = process.argv.slice(2); diff --git a/loan/scripts/inspect.ts b/loan/scripts/inspect.ts index 0185539c..63e4f5b6 100644 --- a/loan/scripts/inspect.ts +++ b/loan/scripts/inspect.ts @@ -1,10 +1,10 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; import { isValidContractAddress } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); interface LoanFlow { from: string; diff --git a/loan/scripts/register.ts b/loan/scripts/register.ts index 85358578..6ec0afc4 100644 --- a/loan/scripts/register.ts +++ b/loan/scripts/register.ts @@ -1,10 +1,10 @@ -import dotenv from 'dotenv'; import { Contract, ethers, EventLog } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); const main = async () => { const args = process.argv.slice(2); diff --git a/loan/scripts/register_source_contract.ts b/loan/scripts/register_source_contract.ts index d0f1dbfd..786c1724 100644 --- a/loan/scripts/register_source_contract.ts +++ b/loan/scripts/register_source_contract.ts @@ -1,10 +1,10 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); const main = async () => { const args = process.argv.slice(2); diff --git a/loan/scripts/repay_loan.ts b/loan/scripts/repay_loan.ts index 03b98843..7587fe91 100644 --- a/loan/scripts/repay_loan.ts +++ b/loan/scripts/repay_loan.ts @@ -1,11 +1,11 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import ERC20Abi from '../../shared/contracts/abi/TestERC20.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); const main = async () => { const args = process.argv.slice(2); diff --git a/loan/scripts/worker.ts b/loan/scripts/worker.ts index 7569a282..ac4aeb75 100644 --- a/loan/scripts/worker.ts +++ b/loan/scripts/worker.ts @@ -1,4 +1,3 @@ -import dotenv from 'dotenv'; import { Contract, ethers } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; @@ -14,8 +13,9 @@ import { submitFundProofToLoanManager, submitRepayProofToLoanManager, } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; -dotenv.config({ override: true }); +loadEnv('loan'); // Graceful shutdown flag let isShuttingDown = false; diff --git a/shared/check_setup.ts b/shared/check_setup.ts index 8dad8742..48ef3d27 100644 --- a/shared/check_setup.ts +++ b/shared/check_setup.ts @@ -1,37 +1,21 @@ -import fs from 'fs'; -import path from 'path'; -import dotenv from 'dotenv'; import { ethers } from 'ethers'; import { chainInfo } from '@gluwa/usc-sdk'; import { isValidContractAddress, isValidPrivateKey } from './utils'; +import { loadEnv } from './env'; -const REPO_ROOT = path.resolve(__dirname, '..'); const VERIFIER_PRECOMPILE = '0x0000000000000000000000000000000000000FD2'; type SetupMode = 'hello' | 'hello-bridge' | 'bridge' | 'custom' | 'loan' | 'network'; -/** Shell/sourced env wins; then tutorial split; then root `.env` fills remaining defaults. */ +/** Loads the same files the tutorial scripts do, so a passing check reflects what they will see. */ function loadTutorialEnv(mode: SetupMode): void { - const snapshot = { ...process.env }; - const files = [path.join(REPO_ROOT, '.env')]; - - if (mode === 'hello' || mode === 'hello-bridge' || mode === 'bridge' || mode === 'custom') { - files.push(path.join(REPO_ROOT, 'bridge', '.env')); - } else if (mode === 'loan') { - files.push(path.join(REPO_ROOT, 'loan', '.env')); - } - - for (const file of files) { - if (fs.existsSync(file)) { - dotenv.config({ path: file, override: true }); - } - } - - for (const [key, value] of Object.entries(snapshot)) { - if (value !== undefined) { - process.env[key] = value; - } + if (mode === 'loan') { + loadEnv('loan'); + } else if (mode === 'network') { + loadEnv(); + } else { + loadEnv('bridge'); } } @@ -91,18 +75,27 @@ async function checkProofBuilder(url: string | undefined): Promise } } +/** ethers surfaces an on-chain revert as CALL_EXCEPTION; anything else is a transport failure. */ +function isExecutionRevert(error: unknown): boolean { + return typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 'CALL_EXCEPTION'; +} + async function checkVerifierPrecompile(creditcoinRpc: string | undefined): Promise { if (!creditcoinRpc?.trim()) { return fail('Verifier precompile: skipped (no CREDITCOIN_RPC_URL)'); } try { const provider = new ethers.JsonRpcProvider(creditcoinRpc); - const code = await provider.getCode(VERIFIER_PRECOMPILE); - if (code && code !== '0x') { + // Frontier precompiles are runtime PrecompileSet entries rather than accounts holding + // bytecode, so `getCode` returns '0x' for them even on Creditcoin. Probe instead: an + // empty call makes the precompile revert on the absent selector, while calling an + // address with nothing behind it succeeds and returns '0x'. + const result = await provider.call({ to: VERIFIER_PRECOMPILE, data: '0x' }); + return fail(`Native verifier precompile (0xFD2): nothing at address, call returned ${result} (wrong network?)`); + } catch (error: unknown) { + if (isExecutionRevert(error)) { return pass('Native verifier precompile (0xFD2): present'); } - return fail('Native verifier precompile (0xFD2): no code at address (wrong network?)'); - } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); return fail(`Verifier precompile check failed (${msg})`); } diff --git a/shared/env.ts b/shared/env.ts new file mode 100644 index 00000000..d2d7b116 --- /dev/null +++ b/shared/env.ts @@ -0,0 +1,41 @@ +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +const REPO_ROOT = path.resolve(__dirname, '..'); + +export type Tutorial = 'bridge' | 'loan'; + +/** + * Loads tutorial configuration into `process.env`. + * + * Config lives in the per-tutorial `bridge/.env` and `loan/.env`; the root `.env` is + * optional and only fills in whatever the tutorial file leaves unset. A bare + * `dotenv.config()` would instead resolve `.env` against `process.cwd()`, which misses + * both tutorial files and depends on the directory the script happens to be run from. + * + * Precedence, highest first: shell/exported env, the tutorial's `.env`, the root `.env`. + * + * @param tutorial Which tutorial's `.env` to layer on top of the root one; omit to load only the root. + */ +export function loadEnv(tutorial?: Tutorial): void { + const shellEnv = { ...process.env }; + + const files = [path.join(REPO_ROOT, '.env')]; + if (tutorial) { + files.push(path.join(REPO_ROOT, tutorial, '.env')); + } + + for (const file of files) { + if (fs.existsSync(file)) { + dotenv.config({ path: file, override: true }); + } + } + + // Anything the caller already exported outranks both files. + for (const [key, value] of Object.entries(shellEnv)) { + if (value !== undefined) { + process.env[key] = value; + } + } +} diff --git a/shared/submit_bridge_query.ts b/shared/submit_bridge_query.ts index 39f30f08..dc7decbb 100644 --- a/shared/submit_bridge_query.ts +++ b/shared/submit_bridge_query.ts @@ -1,4 +1,3 @@ -import dotenv from 'dotenv'; import { Contract, ethers, InterfaceAbi } from 'ethers'; import ASCMinterABI from '../bridge/contracts/abi/ASCMinter.json'; @@ -9,8 +8,9 @@ import { isValidPrivateKey, isValidContractAddress, } from './utils'; +import { loadEnv } from './env'; -dotenv.config({ override: true }); +loadEnv('bridge'); export type BridgeSubmitConfig = { usageCommand: string; From 3731b20be785ea6db2356105d91b171513e76c1c Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:13:52 -0600 Subject: [PATCH 23/31] Fix loan-flow Sepolia deploy funding and payer wallet. Fund the deployer with 0.01 ETH (matching other devnet examples), deploy TestERC20 from the shared CI wallet, and keep AuxiliaryLoanContract on the loan owner key with explicit forge error handling. --- .github/workflows/loan-flow.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 4d8c3773..387a5217 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -185,9 +185,9 @@ jobs: WALLET_PK=$(echo "$OUTPUT" | grep "Private key:" | cut -f2 -d: | tr -d ' ') echo "creditcoin_wallet_pk=$WALLET_PK" >> "$GITHUB_OUTPUT" - echo "INFO: get 0.001 ETH on source chain" + echo "INFO: get 0.01 ETH on source chain" ~/.foundry/bin/cast send "$WALLET_ADDRESS" "0x" \ - --value 1000000000000000 \ + --value 10000000000000000 \ --private-key ${{ steps.env.outputs.source_chain_private_key }} \ --rpc-url "$SOURCE_CHAIN_RPC_URL" @@ -200,22 +200,36 @@ jobs: --rpc-url "$CREDITCOIN_RPC_URL" echo "INFO: deploy TestERC20.sol to source chain" + set +e OUTPUT=$(~/.foundry/bin/forge create \ --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ - --private-key "$WALLET_PK" \ + --private-key ${{ steps.env.outputs.source_chain_private_key }} \ shared/contracts/sol/TestERC20.sol:TestERC20 2>&1) + STATUS=$? + set -e echo "$OUTPUT" + if [ "$STATUS" -ne 0 ]; then + echo "ERROR: TestERC20 forge create failed (exit $STATUS)" + exit "$STATUS" + fi SOURCE_CHAIN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "source_chain_contract_address=$SOURCE_CHAIN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" echo "INFO: deploy AuxiliaryLoanContract.sol to source chain" + set +e OUTPUT=$(~/.foundry/bin/forge create \ --broadcast \ --rpc-url "$SOURCE_CHAIN_RPC_URL" \ --private-key "$WALLET_PK" \ loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract 2>&1) + STATUS=$? + set -e echo "$OUTPUT" + if [ "$STATUS" -ne 0 ]; then + echo "ERROR: AuxiliaryLoanContract forge create failed (exit $STATUS)" + exit "$STATUS" + fi AUX_LOAN_CONTRACT_ADDRESS=$(echo "$OUTPUT" | grep "Deployed to:" | cut -f2 -d: | tr -d " ") echo "aux_loan_contract_address=$AUX_LOAN_CONTRACT_ADDRESS" >> "$GITHUB_OUTPUT" From de0164538b105ce020f82a821f880a0f06c0a3bf Mon Sep 17 00:00:00 2001 From: BradleyOlson64 Date: Tue, 1 Sep 2026 13:59:35 -0700 Subject: [PATCH 24/31] Done with bridge, started loan --- bridge/bridge-offchain-worker/README.md | 2 +- bridge/custom-contracts-bridging/README.md | 25 +++++++++++----------- bridge/hello-bridge/README.md | 9 ++++---- loan/{scripts => }/README.md | 2 +- package.json | 2 +- shared/env.ts | 14 ++++++------ shared/utils/check_balance.js | 5 ++++- 7 files changed, 32 insertions(+), 27 deletions(-) rename loan/{scripts => }/README.md (99%) diff --git a/bridge/bridge-offchain-worker/README.md b/bridge/bridge-offchain-worker/README.md index 007a9dd4..6813f34c 100644 --- a/bridge/bridge-offchain-worker/README.md +++ b/bridge/bridge-offchain-worker/README.md @@ -14,7 +14,7 @@ Implementation: `worker.ts`. Prerequisites: complete [Custom Contract Bridging](../custom-contracts-bridging/README.md) (deployed `ASCMinter`, wrapped token, and `.env` vars). ```sh -source .env +source bridge/.env yarn offchain:start_worker ``` diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index 88a51f16..dc1f7874 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -1,7 +1,7 @@ # Custom Contract Bridging > [!NOTE] -> Deploys the **simplified** `ASCMinter` on shared package `ASCBase` (`@gluwa/asc-contracts` readability) โ€” direct `execute` minting, no relayer contracts. See [bridge/README.md](../README.md). +> This tutorial implements a simplified one-way bridge using Attestcoin readability. Tutorials implementing a full two-way bridge using both readability and writability will be added soon, after the writability core code passes 3rd party audit. > [!TIP] > This tutorial builds on the previous [Hello Bridge] example -make sure to check it out before @@ -33,7 +33,7 @@ address with 1,000,000 `TEST` coins, so we won't have to mint `TEST` tokens manu Make sure to first load your `.env` file with: ```sh -source .env +source bridge/.env ``` After, run the following command to deploy the contract: @@ -56,7 +56,7 @@ Transaction hash: 0xfb0aaf396684bf0727019e5271d7e0dedee1dea9e5a4a1ef7456662d0ac0 Save the contract address shown in `Deployed to:`. You will be needing it in the next step. -Additionally update the `.env` file at the root of the repository with the address, like so: +Additionally update the `.env` file at `bridge/.env` with the address, like so: ```env SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS= @@ -65,7 +65,7 @@ SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS= Once again, reload your `.env` file with: ```sh -source .env +source bridge/.env ``` ## 3. Deploy your bridging stack @@ -74,7 +74,8 @@ You will deploy the stock `ASCMinter` from this repo โ€” **no contract edits req ### 3.1 Deploy `EvmV1Decoder` (once per Creditcoin network) -Deploy the shared decoding library. **Save this address** โ€” you can reuse it for the [Loan Flow](../../loan/scripts/README.md) without deploying again. +> [!NOTE] +> This contract should be pre-deployed and already stored under EVM_V1_DECODER_LIBRARY_ADDRESS in `bridge/.env.example`. So it should already be present in your `bridge/.env` as well. We include how to deploy a new one in case the existing decoder isn't available for your tutorial run. ```bash forge create \ @@ -84,7 +85,7 @@ forge create \ node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder ``` -Add the library address to your root `.env` so later steps (and the loan tutorial) can reuse it: +Add the library address to your `.env` file at `bridge/.env`: ```env EVM_V1_DECODER_LIBRARY_ADDRESS= @@ -93,11 +94,9 @@ EVM_V1_DECODER_LIBRARY_ADDRESS= Reload: ```sh -source .env +source bridge/.env ``` -> **Note:** Deploy from `contracts/common/EvmV1Decoder.sol` in the npm package. That library is a shared receipt decoder โ€” not the Outbox/Relayer bridge stack. - ### 3.2 Deploy `ASCMinter` ```bash @@ -121,7 +120,7 @@ Transaction hash: 0xe86e3c2f77fd050a4120dbd195668af2f3d94f3a41b5db21643c53c1ac3c ### 3.3 Update environment with your ASC contract address -Save the address and update the root `.env`: +Add the address to your `.env` file at `bridge/.env`: ```env ASC_CUSTOM_MINTER_CONTRACT_ADDRESS= @@ -130,7 +129,7 @@ ASC_CUSTOM_MINTER_CONTRACT_ADDRESS= Reload: ```sh -source .env +source bridge/.env ``` ### 3.4 Deploy wrapped token and register the source emitter @@ -146,7 +145,7 @@ forge create \ --constructor-args "$ASC_CUSTOM_MINTER_CONTRACT_ADDRESS" ``` -Update `.env`: +Add the address to your `.env` file at `bridge/.env`: ```env ASC_CUSTOM_MINTABLE_TOKEN= @@ -156,6 +155,7 @@ Register the Sepolia burn contract as an allowed emitter and map it to the wrapp `ASCMinter` mints **only** when the proved burn logโ€™s emitting address was registered with `wrapOriginToken` โ€” a valid proof from any other contract is rejected: ```bash +source bridge/.env cast send \ --rpc-url $CREDITCOIN_RPC_URL \ $ASC_CUSTOM_MINTER_CONTRACT_ADDRESS \ @@ -259,6 +259,7 @@ yarn offchain:start_worker In another terminal, burn tokens โ€” the worker submits the mint proof when attestation completes: ```sh +source bridge/.env cast send --rpc-url $SOURCE_CHAIN_RPC_URL \ $SOURCE_CHAIN_CUSTOM_CONTRACT_ADDRESS \ "burn(uint256)" 2000 \ diff --git a/bridge/hello-bridge/README.md b/bridge/hello-bridge/README.md index 4ce7c963..a8fba585 100644 --- a/bridge/hello-bridge/README.md +++ b/bridge/hello-bridge/README.md @@ -14,14 +14,13 @@ bridging!** The flow is: Hello Bridge uses **shared tutorial contracts** already deployed on CC3 Testnet and Sepolia. The /bridge [`.env.example`](../.env.example) includes pre-filled values for several contracts. Just copy the example env using: ```sh -cd bridge -cp .env.example .env.test +cp bridge/.env.example bridge/.env ``` Then you only need to add **`CREDITCOIN_WALLET_PRIVATE_KEY`** and **`SOURCE_CHAIN_RPC_URL`** (Infura Sepolia), then run: ```sh -source .env +source bridge/.env yarn utils:check_setup hello ``` @@ -76,7 +75,7 @@ CREDITCOIN_WALLET_PRIVATE_KEY= And load into your terminal session with: ```sh -source .env +source bridge/.env ``` ### 1.2 Get some test funds (`Sepolia`) @@ -122,7 +121,7 @@ SOURCE_CHAIN_RPC_URL="https://sepolia.infura.io/v3/" And load into your terminal session with: ```sh -source .env +source bridge/.env ``` ## 2. Minting some tokens on Sepolia diff --git a/loan/scripts/README.md b/loan/README.md similarity index 99% rename from loan/scripts/README.md rename to loan/README.md index 4443dd5f..6c232b94 100644 --- a/loan/scripts/README.md +++ b/loan/README.md @@ -39,7 +39,7 @@ Our system will have three contracts: Make sure to first load your `.env` file with: ```sh -source .env +source loan/.env ``` So, first of all we start with deploying our ERC20 contract: diff --git a/package.json b/package.json index 8a37ced9..df14ac10 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "loan_flow:repay_loan": "tsx loan/scripts/repay_loan.ts", "loan_flow:inspect_loan": "tsx loan/scripts/inspect.ts", "loan_flow:start_worker": "tsx loan/scripts/worker.ts", - "utils:check_balance": "node shared/utils/check_balance.js", + "utils:check_balance": "tsx shared/utils/check_balance.js", "utils:check_setup": "tsx shared/check_setup.ts", "check-format": "prettier --check .", "eslint": "eslint -c .eslintrc.js --ignore-pattern .eslintrc.js --ignore-pattern check_balance.js --ignore-pattern vendor/** --max-warnings 0 --ext .ts --ext .js .", diff --git a/shared/env.ts b/shared/env.ts index d2d7b116..3e0d9587 100644 --- a/shared/env.ts +++ b/shared/env.ts @@ -16,15 +16,17 @@ export type Tutorial = 'bridge' | 'loan'; * * Precedence, highest first: shell/exported env, the tutorial's `.env`, the root `.env`. * - * @param tutorial Which tutorial's `.env` to layer on top of the root one; omit to load only the root. + * CI writes only the root `.env` and never creates the per-tutorial files, so passing a + * tutorial stays correct there โ€” the missing file is skipped. + * + * @param tutorials Which tutorials' `.env` to layer on top of the root one, lowest precedence + * first. Omit to load only the root; pass several for utilities shared by + * more than one tutorial. */ -export function loadEnv(tutorial?: Tutorial): void { +export function loadEnv(...tutorials: Tutorial[]): void { const shellEnv = { ...process.env }; - const files = [path.join(REPO_ROOT, '.env')]; - if (tutorial) { - files.push(path.join(REPO_ROOT, tutorial, '.env')); - } + const files = [path.join(REPO_ROOT, '.env'), ...tutorials.map((t) => path.join(REPO_ROOT, t, '.env'))]; for (const file of files) { if (fs.existsSync(file)) { diff --git a/shared/utils/check_balance.js b/shared/utils/check_balance.js index 06fc540f..f1573c65 100644 --- a/shared/utils/check_balance.js +++ b/shared/utils/check_balance.js @@ -1,6 +1,9 @@ const { ethers } = require('ethers'); -require('dotenv').config({ override: true }); +const { loadEnv } = require('../env'); + +// Shared by both tutorials, so read whichever per-tutorial .env files exist. +loadEnv('bridge', 'loan'); // === Check for arguments === if (process.argv.length < 4 || process.argv.length > 5) { From 78630f794ded733dc21cc218ec575a63a64d24d4 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:17:10 -0600 Subject: [PATCH 25/31] Fixes CI log-range limits and attestation waits limit --- .github/workflows/loan-flow.yml | 55 ++++++++++++++++++--------------- shared/utils/index.ts | 17 +++++++--- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 387a5217..a57b3913 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -332,16 +332,34 @@ jobs: run: | source .env + wait_for_log() { + local pattern="$1" + local file="$2" + local timeout_secs="$3" + local elapsed=0 + while [ "$elapsed" -lt "$timeout_secs" ]; do + if grep -F -q "$pattern" "$file"; then + grep -F "$pattern" "$file" + return 0 + fi + sleep 15 + elapsed=$((elapsed + 15)) + done + echo "ERROR: timed out after ${timeout_secs}s waiting for log line: $pattern" + tail -n 30 "$file" + return 1 + } + echo "INFO: Lender should have 5 tokens initially" OUTPUT=$(yarn utils:check_balance "$SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS" "${{ steps.setup.outputs.lender_wallet_address }}" "$SOURCE_CHAIN_RPC_URL") echo "$OUTPUT" - echo "$OUTPUT" | grep "Raw Balance: 5000000000" + echo "$OUTPUT" | grep "Raw Balance: 5000000000000000000" echo "$OUTPUT" | grep "Formatted Balance: 5.0" echo "INFO: Borrower should have 3 tokens initially" OUTPUT=$(yarn utils:check_balance "$SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS" "${{ steps.setup.outputs.borrower_wallet_address }}" "$SOURCE_CHAIN_RPC_URL") echo "$OUTPUT" - echo "$OUTPUT" | grep "Raw Balance: 3000000000" + echo "$OUTPUT" | grep "Raw Balance: 3000000000000000000" echo "$OUTPUT" | grep "Formatted Balance: 3.0" @@ -371,10 +389,8 @@ jobs: # wait for the off-chain worker to see the event above - # in particular for the source block to be attested to - sleep 90 - grep "Detected LoanRegistered event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log - grep "Registered loan $LOAN_ID for funding on source chain" /var/tmp/loan-flow-logs/offchain-worker.log + wait_for_log "Detected LoanRegistered event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log 180 + wait_for_log "Registered loan $LOAN_ID for funding on source chain" /var/tmp/loan-flow-logs/offchain-worker.log 180 echo "INFO: inspect loan $LOAN_ID" @@ -386,17 +402,11 @@ jobs: echo "$OUTPUT" # wait for the off-chain worker to see the event above - # in particular for the source block to be attested to - sleep 90 - grep "Detected LoanFunded event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log - + wait_for_log "Detected LoanFunded event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log 180 - # wait for off-chain worker to prove the event above - sleep 600 - echo "---- last 3 lines of offchain-worker.log for debugging ----" - tail -n 3 /var/tmp/loan-flow-logs/offchain-worker.log - echo "INFO: Printing LoanFunded result:" - grep "Loan $LOAN_ID has been marked as funded on Creditcoin" /var/tmp/loan-flow-logs/offchain-worker.log + # wait for off-chain worker to prove the event above (~8 min attestation; allow up to 20) + echo "INFO: Waiting for LoanFunded proof on Creditcoin..." + wait_for_log "Loan $LOAN_ID has been marked as funded on Creditcoin" /var/tmp/loan-flow-logs/offchain-worker.log 1200 echo "INFO: inspect loan $LOAN_ID" @@ -408,16 +418,11 @@ jobs: echo "$OUTPUT" - sleep 90 - grep "Detected LoanRepaid event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log - + wait_for_log "Detected LoanRepaid event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log 180 - # wait for off-chain worker to prove the event above - sleep 600 - echo "---- last 3 lines of offchain-worker.log for debugging ----" - tail -n 3 /var/tmp/loan-flow-logs/offchain-worker.log - echo "INFO: Printing LoanRepaid result:" - grep "Loan $LOAN_ID has been marked as fully repaid on Creditcoin" /var/tmp/loan-flow-logs/offchain-worker.log + # wait for off-chain worker to prove the event above (~8 min attestation; allow up to 20) + echo "INFO: Waiting for LoanRepaid proof on Creditcoin..." + wait_for_log "Loan $LOAN_ID has been marked as fully repaid on Creditcoin" /var/tmp/loan-flow-logs/offchain-worker.log 1200 echo "INFO: inspect loan $LOAN_ID" diff --git a/shared/utils/index.ts b/shared/utils/index.ts index c8611785..1dc19a5e 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts @@ -332,6 +332,8 @@ export const POLLING_INTERVAL_MS = 5000; export const ERROR_BACKOFF_MS = 10000; // Maximum number of processed transactions to track before clearing export const MAX_PROCESSED_TXS = 1000; +// Sepolia and other hosted RPCs reject wide eth_getLogs ranges (400 Bad Request). +export const MAX_LOG_BLOCK_RANGE = 2000; // Helper function to poll for events using queryFilter (avoids filter expiration issues) export async function pollEvents( @@ -346,17 +348,22 @@ export async function pollEvents( return fromBlock; } - const events = await contract.queryFilter(eventName, fromBlock, currentBlock); - for (const event of events) { - if (event instanceof EventLog) { - await handler(event); + let start = fromBlock; + while (start <= currentBlock) { + const end = Math.min(start + MAX_LOG_BLOCK_RANGE - 1, currentBlock); + const events = await contract.queryFilter(eventName, start, end); + for (const event of events) { + if (event instanceof EventLog) { + await handler(event); + } } + start = end + 1; } // Return next block to query from return currentBlock + 1; } catch (error: any) { - console.error(`Error polling ${eventName} events:`, error.shortMessage); + console.error(`Error polling ${eventName} events:`, error.shortMessage ?? error.message); // Add backoff delay on error to avoid hammering the RPC await new Promise((resolve) => setTimeout(resolve, ERROR_BACKOFF_MS)); return fromBlock; // Retry from same block on error From bbadf26a4199fe609ada7cf85aa367a28b4bc7bc Mon Sep 17 00:00:00 2001 From: BradleyOlson64 Date: Tue, 1 Sep 2026 14:20:56 -0700 Subject: [PATCH 26/31] Loan flow complete --- loan/.env.example | 4 +++- loan/README.md | 45 +++++++++++++++++---------------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/loan/.env.example b/loan/.env.example index 3e69c066..c3da0489 100644 --- a/loan/.env.example +++ b/loan/.env.example @@ -5,7 +5,10 @@ SOURCE_CHAIN_KEY=1 PROOF_BUILDER_URL="https://prover.cc3-testnet.creditcoin.network" CREDITCOIN_RPC_URL="https://rpc.cc3-testnet.creditcoin.network" EVM_V1_DECODER_LIBRARY_ADDRESS="0x04B9ae8562D8Cc5bbbBbBB759080dDC30B56D18B" + +## User-provided SOURCE_CHAIN_RPC_URL="" +CREDITCOIN_WALLET_PRIVATE_KEY="" ASC_LOAN_MANAGER_CONTRACT_ADDRESS="" SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS="" @@ -13,4 +16,3 @@ SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS="" LENDER_WALLET_PRIVATE_KEY="" BORROWER_WALLET_PRIVATE_KEY="" -CREDITCOIN_WALLET_PRIVATE_KEY="" diff --git a/loan/README.md b/loan/README.md index 6c232b94..af0a0140 100644 --- a/loan/README.md +++ b/loan/README.md @@ -28,6 +28,16 @@ yarn install The setup of this example is a bit more complex than previous ones. So let's go slowly: +## 1.0 .env fields + +First, we need to fill in our loan/.env with some fields used in prior tutorials: + +```env +## User-provided +SOURCE_CHAIN_RPC_URL="" +CREDITCOIN_WALLET_PRIVATE_KEY="" +``` + ## 1.1 Smart contracts Our system will have three contracts: @@ -60,34 +70,13 @@ Deployed to: 0x814Fd6EfA5E1cb49B623599Fd63Ab74142762A46 Transaction hash: 0xb234238d0da392063395ef59195b25827cdaf2009fe773fed1f8f7eda99fab7b ``` -Grab the contract address and add it to the `.env` file at the root of the repository with the address, like so: +Grab the contract address and add it to the `loan/.env` file at the root of the repository with the address, like so: ```env SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS= ``` -Now we need to deploy `ASCLoanManager`. It links the same **`EvmV1Decoder`** library as the bridge tutorial. - -> [!TIP] -> If you completed [Custom Contract Bridging](../../bridge/custom-contracts-bridging/README.md), reuse `EVM_V1_DECODER_LIBRARY_ADDRESS` from your `.env` โ€” **skip redeploying the decoder**. - -If you have not deployed a decoder yet: - -```bash -forge create \ - --broadcast \ - --rpc-url $CREDITCOIN_RPC_URL \ - --private-key $CREDITCOIN_WALLET_PRIVATE_KEY \ - node_modules/@gluwa/asc-contracts/contracts/common/EvmV1Decoder.sol:EvmV1Decoder -``` - -Save the address in `.env`: - -```env -EVM_V1_DECODER_LIBRARY_ADDRESS= -``` - -Use the contract address shown in `Deployed to:` to deploy your `ASCLoanManager` using the following command: +Now deploy your `ASCLoanManager` using the following command: ```sh forge create \ @@ -100,7 +89,7 @@ forge create \ You should get deployment output with your `ASCLoanManager` address. -As before, grab the address and add it to the `.env` file, like so: +As before, grab the address and add it to the `loan/.env` file, like so: ```env ASC_LOAN_MANAGER_CONTRACT_ADDRESS= @@ -116,7 +105,7 @@ forge create \ loan/contracts/sol/AuxiliaryLoanContract.sol:AuxiliaryLoanContract ``` -Grab the address again (it gets repetitive huh?) and add it to the `.env` file, like so: +Grab the address again (it gets repetitive huh?) and add it to the `loan/.env` file, like so: ```env SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS= @@ -126,7 +115,7 @@ Now that we have all three contracts we can move on to the actual participants. ## 1.2 Lender and borrower -Our system has both the lender account and the borrower fixed, much like before they are set in the `.env` file. +Our system has both the lender account and the borrower fixed, much like before they are set in the `loan/.env` file. But of course, you only have one account at this point, you can reuse the account in `CREDITCOIN_WALLET_PRIVATE_KEY` for the lender, for the borrower you can just create a new one like we did in [Hello Bridge], like so: @@ -161,7 +150,7 @@ Now that we've populated our .env file with all necessary variables, let's load those variables into our terminal. ```sh -source .env +source loan/.env ``` ## 1.3 Funding accounts @@ -266,7 +255,7 @@ Now that our worker is up and running, let's register a loan! Open a new terminal window and source the `.env` file once again ```sh -source .env +source loan/.env ``` Then run the following command to register a loan: From d113b561ecd23e925b04460677264e10d68ad321 Mon Sep 17 00:00:00 2001 From: BradleyOlson64 Date: Tue, 1 Sep 2026 14:27:04 -0700 Subject: [PATCH 27/31] Fix readme links --- bridge/bridge-offchain-worker/README.md | 3 +-- loan/README.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bridge/bridge-offchain-worker/README.md b/bridge/bridge-offchain-worker/README.md index 6813f34c..a3b92d78 100644 --- a/bridge/bridge-offchain-worker/README.md +++ b/bridge/bridge-offchain-worker/README.md @@ -28,7 +28,6 @@ Burn on Sepolia in another terminal; the worker handles attestation, proof fetch ## Next -[Loan Flow](../../loan/scripts/README.md) โ€” cross-chain loan state with a similar worker pattern. +[Loan Flow](../../loan/README.md) โ€” cross-chain loan state with a similar worker pattern. [Custom Contract Bridging]: ../custom-contracts-bridging/README.md -[Loan Flow]: ../../loan/scripts/README.md diff --git a/loan/README.md b/loan/README.md index af0a0140..1bc33780 100644 --- a/loan/README.md +++ b/loan/README.md @@ -1,7 +1,7 @@ # Loan Flow > [!TIP] -> Complete [Custom Contract Bridging](../../bridge/custom-contracts-bridging/README.md) (and optionally the offchain worker in ยง7) before this tutorial. +> Complete [Custom Contract Bridging](../bridge/custom-contracts-bridging/README.md) (and optionally the offchain worker in ยง7) before this tutorial. Now that we know how to build an offchain worker to coordinate between two separate chains, we need a more advanced example that includes not only communication but also state tracking. @@ -488,6 +488,6 @@ You've learned: If you haven't already, take a look at the [ASC Gitbook] for more information. -[Hello Bridge]: ../../bridge/hello-bridge/README.md#11-generate-a-new-wallet-address +[Hello Bridge]: ../bridge/hello-bridge/README.md#11-generate-a-new-wallet-address [ASC Gitbook]: https://docs.attestcoin.org/attestcoin-protocol [๐Ÿšฐ testnet faucet]: https://cloud.google.com/application/web3/faucet/ethereum/sepolia From 6fd4b117668b1f05ec6646974b9947c67801bc67 Mon Sep 17 00:00:00 2001 From: BradleyOlson64 Date: Tue, 1 Sep 2026 14:31:09 -0700 Subject: [PATCH 28/31] Fixed more readme links --- README.md | 2 +- bridge/custom-contracts-bridging/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7b54742..330db5cf 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ We recommend going through them in this order: [Hello Bridge]: ./bridge/hello-bridge/README.md [Custom Contracts Bridging]: ./bridge/custom-contracts-bridging/README.md [Bridge Offchain Worker]: ./bridge/bridge-offchain-worker/README.md -[Loan Flow]: ./loan/scripts/README.md +[Loan Flow]: ./loan/README.md [ASC Architecture Overview]: https://docs.attestcoin.org/attestcoin-protocol/architecture [DApp Builder Infrastructure]: https://docs.attestcoin.org/attestcoin-protocol/dapp-builder-infrastructure [Attestcoin Readability Subsystems]: https://docs.attestcoin.org/attestcoin-protocol/attestcoin-readability diff --git a/bridge/custom-contracts-bridging/README.md b/bridge/custom-contracts-bridging/README.md index dc1f7874..c4edcfe8 100644 --- a/bridge/custom-contracts-bridging/README.md +++ b/bridge/custom-contracts-bridging/README.md @@ -276,7 +276,7 @@ To experiment with customizing ASC logic, edit `bridge/contracts/sol/ASCMinter.s Congratulations! You've deployed your own simplified ASC bridge (`ASCMinter` + wrapped token), registered the Sepolia burn emitter, and completed a trustless mint. -Next: [Loan Flow](../../loan/scripts/README.md) reuses your decoder library and extends the pattern with cross-chain loan state. +Next: [Loan Flow](../../loan/README.md) reuses your decoder library and extends the pattern with cross-chain loan state. [Hello Bridge]: ../hello-bridge/README.md [setup]: ../hello-bridge/README.md#1-setup From bf5ad30bfd6aa322e869a5611915179c5c97e88c Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:43:21 -0600 Subject: [PATCH 29/31] Proof submission without Sepolia eth_getLogs polling to avoid timeout error in CI --- .github/workflows/loan-flow.yml | 19 ++--- .lycheeignore | 1 + loan/scripts/fund_loan.ts | 3 + loan/scripts/loan_proof.ts | 125 ++++++++++++++++++++++++++++++++ loan/scripts/repay_loan.ts | 3 + loan/scripts/worker.ts | 26 ++++++- shared/utils/index.ts | 38 +++++++--- 7 files changed, 188 insertions(+), 27 deletions(-) create mode 100644 loan/scripts/loan_proof.ts diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index a57b3913..e71bcf85 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -28,6 +28,7 @@ permissions: read-all jobs: example: runs-on: ${{ fromJSON(inputs.runs-on) }} + timeout-minutes: 90 steps: - name: Anvil ENV id: anvil @@ -400,13 +401,8 @@ jobs: echo "INFO: Lender completely funds the loan" OUTPUT=$(yarn loan_flow:fund_loan "$LOAN_ID" 1000) echo "$OUTPUT" - - # wait for the off-chain worker to see the event above - wait_for_log "Detected LoanFunded event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log 180 - - # wait for off-chain worker to prove the event above (~8 min attestation; allow up to 20) - echo "INFO: Waiting for LoanFunded proof on Creditcoin..." - wait_for_log "Loan $LOAN_ID has been marked as funded on Creditcoin" /var/tmp/loan-flow-logs/offchain-worker.log 1200 + echo "$OUTPUT" | grep "Loan funded:" + echo "$OUTPUT" | grep "Loan $LOAN_ID has been marked as funded on Creditcoin." echo "INFO: inspect loan $LOAN_ID" @@ -416,13 +412,8 @@ jobs: echo "INFO: Borrower repays loan + interest" OUTPUT=$(yarn loan_flow:repay_loan "$LOAN_ID" 1050) echo "$OUTPUT" - - - wait_for_log "Detected LoanRepaid event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log 180 - - # wait for off-chain worker to prove the event above (~8 min attestation; allow up to 20) - echo "INFO: Waiting for LoanRepaid proof on Creditcoin..." - wait_for_log "Loan $LOAN_ID has been marked as fully repaid on Creditcoin" /var/tmp/loan-flow-logs/offchain-worker.log 1200 + echo "$OUTPUT" | grep "Loan repaid:" + echo "$OUTPUT" | grep "Loan $LOAN_ID has been marked as fully repaid on Creditcoin." echo "INFO: inspect loan $LOAN_ID" diff --git a/.lycheeignore b/.lycheeignore index 48675dbb..c2bf8c00 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -31,3 +31,4 @@ https://docs.creditcoin.org/attestcoin-protocol(.*) https://docs\.attestcoin\.org https://www\.npmjs\.com/package/@gluwa/asc-contracts https://cloud\.google\.com/application/web3/faucet/ethereum/sepolia +https://discord\.com diff --git a/loan/scripts/fund_loan.ts b/loan/scripts/fund_loan.ts index 7b70dfea..ec381c60 100644 --- a/loan/scripts/fund_loan.ts +++ b/loan/scripts/fund_loan.ts @@ -4,6 +4,7 @@ import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import ERC20Abi from '../../shared/contracts/abi/TestERC20.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; import { loadEnv } from '../../shared/env'; +import { submitLoanProofAfterTx } from './loan_proof'; loadEnv('loan'); @@ -117,6 +118,8 @@ const main = async () => { console.log('Funding transaction submitted, waiting for it to be mined: ', tx.hash); await tx.wait(); console.log('Loan funded: ', tx.hash); + + await submitLoanProofAfterTx(loanId, tx.hash, 'fund'); } catch (error: any) { console.error('Error funding loan: ', error.shortMessage); process.exit(1); diff --git a/loan/scripts/loan_proof.ts b/loan/scripts/loan_proof.ts new file mode 100644 index 00000000..0b7f219d --- /dev/null +++ b/loan/scripts/loan_proof.ts @@ -0,0 +1,125 @@ +import { Contract, ethers } from 'ethers'; + +import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; +import { + computeGasLimitForLoanManager, + generateProofFor, + isValidContractAddress, + isValidPrivateKey, + submitFundProofToLoanManager, + submitRepayProofToLoanManager, +} from '../../shared/utils'; + +/** Matches LoanStatus in LoanTypes.sol */ +const LOAN_STATUS_CREATED = 0; +const LOAN_STATUS_FUNDED = 1; +const LOAN_STATUS_PARTLY_REPAID = 2; +const LOAN_STATUS_REPAID = 3; + +export type LoanProofKind = 'fund' | 'repay'; + +async function getLoanStatus(managerContract: Contract, loanId: number): Promise { + const order = await managerContract.getLoanOrder(loanId); + return Number(order[6]); +} + +/** + * Prove a source-chain fund/repay tx and submit to ASCLoanManager on Creditcoin. + * Uses the tx hash + proof builder (no wide eth_getLogs scans on Sepolia). + */ +export async function submitLoanProofAfterTx(loanId: number, txHash: string, kind: LoanProofKind): Promise { + const proofBuilderUrl = process.env.PROOF_BUILDER_URL; + const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; + const ccNextRpcUrl = process.env.CREDITCOIN_RPC_URL; + const ccNextWalletPrivateKey = process.env.CREDITCOIN_WALLET_PRIVATE_KEY; + const loanManagerContractAddress = process.env.ASC_LOAN_MANAGER_CONTRACT_ADDRESS; + const sourceChainKey = Number(process.env.SOURCE_CHAIN_KEY); + + if (!proofBuilderUrl) { + throw new Error('PROOF_BUILDER_URL environment variable is not configured or invalid'); + } + if (!sourceChainRpcUrl) { + throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); + } + if (!ccNextRpcUrl) { + throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); + } + if (!isValidPrivateKey(ccNextWalletPrivateKey)) { + throw new Error('CREDITCOIN_WALLET_PRIVATE_KEY environment variable is not configured or invalid'); + } + if (!isValidContractAddress(loanManagerContractAddress)) { + throw new Error('ASC_LOAN_MANAGER_CONTRACT_ADDRESS environment variable is not configured or invalid'); + } + if (isNaN(sourceChainKey)) { + throw new Error('SOURCE_CHAIN_KEY environment variable is not configured or invalid'); + } + + const ccProvider = new ethers.JsonRpcProvider(ccNextRpcUrl); + const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); + const ccWallet = new ethers.Wallet(ccNextWalletPrivateKey!, ccProvider); + const managerContract = new Contract(loanManagerContractAddress!, loanManagerAbi, ccWallet); + + const status = await getLoanStatus(managerContract, loanId); + + if (kind === 'fund') { + if (status !== LOAN_STATUS_CREATED) { + console.log( + `Loan ${loanId} is not in Created status on Creditcoin (status=${status}), skipping fund proof submission.` + ); + if (status === LOAN_STATUS_FUNDED || status === LOAN_STATUS_PARTLY_REPAID) { + console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); + } + return; + } + } else { + if (status === LOAN_STATUS_REPAID) { + console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); + return; + } + if (status !== LOAN_STATUS_FUNDED && status !== LOAN_STATUS_PARTLY_REPAID) { + throw new Error(`Loan ${loanId} is not in a repayable status on Creditcoin (status=${status})`); + } + } + + console.log( + `Detected ${kind === 'fund' ? 'LoanFunded' : 'LoanRepaid'} event for loanId: ${loanId}, tx hash: ${txHash}` + ); + + const proofResult = await generateProofFor(txHash, sourceChainKey, proofBuilderUrl, ccProvider, sourceChainProvider); + + if (!proofResult.success) { + throw new Error(`Failed to generate proof: ${proofResult.error}`); + } + + const isRepayment = kind === 'repay'; + const gasLimit = await computeGasLimitForLoanManager( + ccProvider, + managerContract, + proofResult.data!, + ccWallet.address, + isRepayment + ); + + const response = isRepayment + ? await submitRepayProofToLoanManager(managerContract, proofResult.data!, gasLimit) + : await submitFundProofToLoanManager(managerContract, proofResult.data!, gasLimit); + + await response.wait?.(); + + if (kind === 'fund') { + console.log(`Marked loan ${loanId} as funded on Creditcoin, tx hash: ${response.hash}`); + console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); + return; + } + + console.log(`Marked loan ${loanId} as repaid on Creditcoin, tx hash: ${response.hash}`); + + const newStatus = await getLoanStatus(managerContract, loanId); + if (newStatus === LOAN_STATUS_REPAID) { + console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); + } else if (newStatus === LOAN_STATUS_PARTLY_REPAID) { + const order = await managerContract.getLoanOrder(loanId); + const repaidAmount = order[7]; + console.log(`Loan ${loanId} has been partially repaid on Creditcoin. Amount repaid: ${repaidAmount}`); + } +} diff --git a/loan/scripts/repay_loan.ts b/loan/scripts/repay_loan.ts index 7587fe91..ecc0cf79 100644 --- a/loan/scripts/repay_loan.ts +++ b/loan/scripts/repay_loan.ts @@ -4,6 +4,7 @@ import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import ERC20Abi from '../../shared/contracts/abi/TestERC20.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; import { loadEnv } from '../../shared/env'; +import { submitLoanProofAfterTx } from './loan_proof'; loadEnv('loan'); @@ -117,6 +118,8 @@ const main = async () => { console.log('Repayment transaction submitted, waiting for it to be mined: ', tx.hash); await tx.wait(); console.log('Loan repaid: ', tx.hash); + + await submitLoanProofAfterTx(loanId, tx.hash, 'repay'); } catch (error: any) { console.error('Error repaying loan: ', error); process.exit(1); diff --git a/loan/scripts/worker.ts b/loan/scripts/worker.ts index ac4aeb75..23a99c3b 100644 --- a/loan/scripts/worker.ts +++ b/loan/scripts/worker.ts @@ -218,6 +218,15 @@ const main = async () => { return; } + const onChainOrder = await managerContract.getLoanOrder(loanId); + const onChainStatus = Number(onChainOrder[6]); + if (onChainStatus !== 0) { + console.log(`Loan ${loanId} already marked on Creditcoin (status=${onChainStatus}), skipping fund proof.`); + loanInfo.funded = onChainStatus >= 1 && onChainStatus !== 4; + processedTxs.add(txHash); + return; + } + // 6.1 Validate transaction and generate proof once the block is attested const proofResult = await generateProofFor( txHash, @@ -247,7 +256,7 @@ const main = async () => { console.error(`Error on LoanFunded handle for ${loanId}: `, error.shortMessage); } } else { - throw new Error(`Failed to generate proof: ${proofResult.error}`); + console.error(`Failed to generate proof for LoanFunded ${loanId}: ${proofResult.error}`); } }), // 7. Poll LoanRepaid events on source chain loan contract @@ -273,6 +282,19 @@ const main = async () => { return; } + const onChainOrder = await managerContract.getLoanOrder(loanId); + const onChainStatus = Number(onChainOrder[6]); + if (onChainStatus === 3) { + console.log(`Loan ${loanId} already fully repaid on Creditcoin, skipping repay proof.`); + loanInfo.repaid = true; + processedTxs.add(txHash); + return; + } + if (onChainStatus !== 1 && onChainStatus !== 2) { + console.warn(`Loan ${loanId} not repayable on Creditcoin (status=${onChainStatus}). Skipping.`); + return; + } + // 7.1 Validate transaction and generate proof once the block is attested const proofResult = await generateProofFor( txHash, @@ -308,7 +330,7 @@ const main = async () => { console.error(`Error on LoanRepaid handle for ${loanId}: `, error.shortMessage); } } else { - throw new Error(`Failed to generate proof: ${proofResult.error}`); + console.error(`Failed to generate proof for LoanRepaid ${loanId}: ${proofResult.error}`); } }), // 8. Polling LoanFunded, LoanExpired, LoanPartiallyRepaid and LoanRepaid events on manager contract diff --git a/shared/utils/index.ts b/shared/utils/index.ts index 1dc19a5e..f65c4d25 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts @@ -332,8 +332,8 @@ export const POLLING_INTERVAL_MS = 5000; export const ERROR_BACKOFF_MS = 10000; // Maximum number of processed transactions to track before clearing export const MAX_PROCESSED_TXS = 1000; -// Sepolia and other hosted RPCs reject wide eth_getLogs ranges (400 Bad Request). -export const MAX_LOG_BLOCK_RANGE = 2000; +// Hosted Sepolia RPCs often cap eth_getLogs (Google 400, others ~50 blocks). +export const MAX_LOG_BLOCK_RANGE = 50; // Helper function to poll for events using queryFilter (avoids filter expiration issues) export async function pollEvents( @@ -349,24 +349,40 @@ export async function pollEvents( } let start = fromBlock; + let chunkSize = MAX_LOG_BLOCK_RANGE; + while (start <= currentBlock) { - const end = Math.min(start + MAX_LOG_BLOCK_RANGE - 1, currentBlock); - const events = await contract.queryFilter(eventName, start, end); - for (const event of events) { - if (event instanceof EventLog) { - await handler(event); + const end = Math.min(start + chunkSize - 1, currentBlock); + try { + const events = await contract.queryFilter(eventName, start, end); + for (const event of events) { + if (event instanceof EventLog) { + await handler(event); + } + } + start = end + 1; + chunkSize = MAX_LOG_BLOCK_RANGE; + } catch (chunkError: any) { + const message = chunkError.shortMessage ?? chunkError.message ?? String(chunkError); + console.error(`Error polling ${eventName} events (blocks ${start}-${end}):`, message); + + if (chunkSize > 1) { + chunkSize = Math.max(1, Math.floor(chunkSize / 2)); + await new Promise((resolve) => setTimeout(resolve, ERROR_BACKOFF_MS)); + } else { + // Skip a bad block instead of retrying forever on the same range. + start = end + 1; + chunkSize = MAX_LOG_BLOCK_RANGE; + await new Promise((resolve) => setTimeout(resolve, ERROR_BACKOFF_MS)); } } - start = end + 1; } - // Return next block to query from return currentBlock + 1; } catch (error: any) { console.error(`Error polling ${eventName} events:`, error.shortMessage ?? error.message); - // Add backoff delay on error to avoid hammering the RPC await new Promise((resolve) => setTimeout(resolve, ERROR_BACKOFF_MS)); - return fromBlock; // Retry from same block on error + return fromBlock; } } From f3d5dd9f4680ad6072d1ad6c249d6cf334605d39 Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:46:19 -0600 Subject: [PATCH 30/31] Avoid duplicate fund proof race between worker and fund_loan script in CI. Re-check Creditcoin loan status after attestation waits, skip worker fund/repay proof submission when scripts handle proofs, and sync worker tracker from chain. --- .github/workflows/loan-flow.yml | 1 + loan/scripts/fund_loan.ts | 2 +- loan/scripts/loan_proof.ts | 21 +++++++++++++ loan/scripts/repay_loan.ts | 2 +- loan/scripts/worker.ts | 53 +++++++++++++++++++++++++++++++-- 5 files changed, 74 insertions(+), 5 deletions(-) diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index e71bcf85..50fa67be 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -328,6 +328,7 @@ jobs: echo "CREDITCOIN_WALLET_PRIVATE_KEY='${{ steps.setup.outputs.creditcoin_wallet_pk }}'" >> .env echo "LENDER_WALLET_PRIVATE_KEY='${{ steps.setup.outputs.lender_wallet_pk }}'" >> .env echo "BORROWER_WALLET_PRIVATE_KEY='${{ steps.setup.outputs.borrower_wallet_pk }}'" >> .env + echo "LOAN_WORKER_SKIP_FUND_REPAY_PROOF=1" >> .env - name: Exercise example run: | diff --git a/loan/scripts/fund_loan.ts b/loan/scripts/fund_loan.ts index ec381c60..6a5b9b66 100644 --- a/loan/scripts/fund_loan.ts +++ b/loan/scripts/fund_loan.ts @@ -121,7 +121,7 @@ const main = async () => { await submitLoanProofAfterTx(loanId, tx.hash, 'fund'); } catch (error: any) { - console.error('Error funding loan: ', error.shortMessage); + console.error('Error funding loan: ', error.shortMessage ?? error.message ?? error); process.exit(1); } diff --git a/loan/scripts/loan_proof.ts b/loan/scripts/loan_proof.ts index 0b7f219d..df4f053b 100644 --- a/loan/scripts/loan_proof.ts +++ b/loan/scripts/loan_proof.ts @@ -91,6 +91,27 @@ export async function submitLoanProofAfterTx(loanId: number, txHash: string, kin throw new Error(`Failed to generate proof: ${proofResult.error}`); } + // Attestation can take several minutes; the off-chain worker may have submitted the proof while we waited. + const statusAfterProof = await getLoanStatus(managerContract, loanId); + + if (kind === 'fund') { + if (statusAfterProof !== LOAN_STATUS_CREATED) { + if (statusAfterProof === LOAN_STATUS_FUNDED || statusAfterProof === LOAN_STATUS_PARTLY_REPAID) { + console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); + } else { + console.log( + `Loan ${loanId} is no longer in Created status on Creditcoin after proof wait (status=${statusAfterProof}).` + ); + } + return; + } + } else if (statusAfterProof === LOAN_STATUS_REPAID) { + console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); + return; + } else if (statusAfterProof !== LOAN_STATUS_FUNDED && statusAfterProof !== LOAN_STATUS_PARTLY_REPAID) { + throw new Error(`Loan ${loanId} is not in a repayable status after proof wait (status=${statusAfterProof})`); + } + const isRepayment = kind === 'repay'; const gasLimit = await computeGasLimitForLoanManager( ccProvider, diff --git a/loan/scripts/repay_loan.ts b/loan/scripts/repay_loan.ts index ecc0cf79..57a9cba2 100644 --- a/loan/scripts/repay_loan.ts +++ b/loan/scripts/repay_loan.ts @@ -121,7 +121,7 @@ const main = async () => { await submitLoanProofAfterTx(loanId, tx.hash, 'repay'); } catch (error: any) { - console.error('Error repaying loan: ', error); + console.error('Error repaying loan: ', error.shortMessage ?? error.message ?? error); process.exit(1); } diff --git a/loan/scripts/worker.ts b/loan/scripts/worker.ts index 23a99c3b..7703e1c3 100644 --- a/loan/scripts/worker.ts +++ b/loan/scripts/worker.ts @@ -228,6 +228,13 @@ const main = async () => { } // 6.1 Validate transaction and generate proof once the block is attested + if (process.env.LOAN_WORKER_SKIP_FUND_REPAY_PROOF === '1') { + console.log(`Skipping fund proof in worker (scripts submit proofs); loan ${loanId} funded on source chain.`); + loanInfo.funded = true; + processedTxs.add(txHash); + return; + } + const proofResult = await generateProofFor( txHash, sourceChainKey, @@ -237,6 +244,16 @@ const main = async () => { ); if (proofResult.success) { + const statusAfterProof = Number((await managerContract.getLoanOrder(loanId))[6]); + if (statusAfterProof !== 0) { + console.log( + `Loan ${loanId} already marked on Creditcoin after proof wait (status=${statusAfterProof}), skipping fund proof.` + ); + loanInfo.funded = statusAfterProof >= 1 && statusAfterProof !== 4; + processedTxs.add(txHash); + return; + } + try { // 6.2 Mark loan as funded on Creditcoin const gasLimit = await computeGasLimitForLoanManager( @@ -277,13 +294,23 @@ const main = async () => { return; } - if (loanInfo.expired || loanInfo.repaid || !loanInfo.funded) { + const onChainOrder = await managerContract.getLoanOrder(loanId); + const onChainStatus = Number(onChainOrder[6]); + + if (loanInfo.expired || loanInfo.repaid) { console.warn(`Loan ${loanId} is not in a valid state for repayment. Skipping.`); return; } - const onChainOrder = await managerContract.getLoanOrder(loanId); - const onChainStatus = Number(onChainOrder[6]); + if (!loanInfo.funded && onChainStatus >= 1 && onChainStatus !== 4) { + loanInfo.funded = true; + } + + if (!loanInfo.funded) { + console.warn(`Loan ${loanId} is not funded in worker tracker yet. Skipping repay proof.`); + return; + } + if (onChainStatus === 3) { console.log(`Loan ${loanId} already fully repaid on Creditcoin, skipping repay proof.`); loanInfo.repaid = true; @@ -295,6 +322,12 @@ const main = async () => { return; } + if (process.env.LOAN_WORKER_SKIP_FUND_REPAY_PROOF === '1') { + console.log(`Skipping repay proof in worker (scripts submit proofs); loan ${loanId} repaid on source chain.`); + processedTxs.add(txHash); + return; + } + // 7.1 Validate transaction and generate proof once the block is attested const proofResult = await generateProofFor( txHash, @@ -305,6 +338,20 @@ const main = async () => { ); if (proofResult.success) { + const statusAfterProof = Number((await managerContract.getLoanOrder(loanId))[6]); + if (statusAfterProof === 3) { + console.log(`Loan ${loanId} already fully repaid on Creditcoin after proof wait, skipping repay proof.`); + loanInfo.repaid = true; + processedTxs.add(txHash); + return; + } + if (statusAfterProof !== 1 && statusAfterProof !== 2) { + console.warn( + `Loan ${loanId} not repayable on Creditcoin after proof wait (status=${statusAfterProof}). Skipping.` + ); + return; + } + try { // 7.2 Note loan repayment on Creditcoin, depending on whether the loan is fully repaid or not // will trigger either partial or full repayment events From 66889b3cdc51170f41c19510174f68a77d363b8e Mon Sep 17 00:00:00 2001 From: Ray Ouyang <12910022+ohyung1287@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:55:50 -0600 Subject: [PATCH 31/31] Stabilize loan-flow CI: drop worker, verify on-chain status Register source-chain funding in register_loan for CI, poll Creditcoin loan status instead of grepping script output, retry proof execute, and remove fragile formatted-balance checks. --- .github/workflows/loan-flow.yml | 37 +------- loan/scripts/loan_proof.ts | 156 ++++++++++++++++++++----------- loan/scripts/register.ts | 43 +++++++-- loan/scripts/wait_loan_status.ts | 86 +++++++++++++++++ package.json | 1 + 5 files changed, 228 insertions(+), 95 deletions(-) create mode 100644 loan/scripts/wait_loan_status.ts diff --git a/.github/workflows/loan-flow.yml b/.github/workflows/loan-flow.yml index 50fa67be..6b09b467 100644 --- a/.github/workflows/loan-flow.yml +++ b/.github/workflows/loan-flow.yml @@ -328,30 +328,12 @@ jobs: echo "CREDITCOIN_WALLET_PRIVATE_KEY='${{ steps.setup.outputs.creditcoin_wallet_pk }}'" >> .env echo "LENDER_WALLET_PRIVATE_KEY='${{ steps.setup.outputs.lender_wallet_pk }}'" >> .env echo "BORROWER_WALLET_PRIVATE_KEY='${{ steps.setup.outputs.borrower_wallet_pk }}'" >> .env - echo "LOAN_WORKER_SKIP_FUND_REPAY_PROOF=1" >> .env + echo "LOAN_REGISTER_SOURCE_FUNDING_IN_REGISTER=1" >> .env - name: Exercise example run: | source .env - wait_for_log() { - local pattern="$1" - local file="$2" - local timeout_secs="$3" - local elapsed=0 - while [ "$elapsed" -lt "$timeout_secs" ]; do - if grep -F -q "$pattern" "$file"; then - grep -F "$pattern" "$file" - return 0 - fi - sleep 15 - elapsed=$((elapsed + 15)) - done - echo "ERROR: timed out after ${timeout_secs}s waiting for log line: $pattern" - tail -n 30 "$file" - return 1 - } - echo "INFO: Lender should have 5 tokens initially" OUTPUT=$(yarn utils:check_balance "$SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS" "${{ steps.setup.outputs.lender_wallet_address }}" "$SOURCE_CHAIN_RPC_URL") echo "$OUTPUT" @@ -377,24 +359,15 @@ jobs: echo "$OUTPUT" | grep "Source loan contract registered with transaction hash:" - echo "INFO: start offchain worker" - yarn loan_flow:start_worker > /var/tmp/loan-flow-logs/offchain-worker.log 2>&1 & - sleep 10 - - echo "INFO: Register a loan" # , , OUTPUT=$(yarn loan_flow:register_loan 1000 500 1000) echo "$OUTPUT" echo "$OUTPUT" | grep "Loan registered with transaction hash:" + echo "$OUTPUT" | grep "Registered loan .* for funding on source chain" LOAN_ID=$(echo "$OUTPUT" | grep "Loan successfully registered with ID:" | cut -f2 -d: | tr -d ' ') - # wait for the off-chain worker to see the event above - wait_for_log "Detected LoanRegistered event for loanId: $LOAN_ID" /var/tmp/loan-flow-logs/offchain-worker.log 180 - wait_for_log "Registered loan $LOAN_ID for funding on source chain" /var/tmp/loan-flow-logs/offchain-worker.log 180 - - echo "INFO: inspect loan $LOAN_ID" yarn loan_flow:inspect_loan "$LOAN_ID" @@ -403,7 +376,7 @@ jobs: OUTPUT=$(yarn loan_flow:fund_loan "$LOAN_ID" 1000) echo "$OUTPUT" echo "$OUTPUT" | grep "Loan funded:" - echo "$OUTPUT" | grep "Loan $LOAN_ID has been marked as funded on Creditcoin." + yarn loan_flow:wait_loan_status "$LOAN_ID" Funded 1500 echo "INFO: inspect loan $LOAN_ID" @@ -414,7 +387,7 @@ jobs: OUTPUT=$(yarn loan_flow:repay_loan "$LOAN_ID" 1050) echo "$OUTPUT" echo "$OUTPUT" | grep "Loan repaid:" - echo "$OUTPUT" | grep "Loan $LOAN_ID has been marked as fully repaid on Creditcoin." + yarn loan_flow:wait_loan_status "$LOAN_ID" Repaid 1500 echo "INFO: inspect loan $LOAN_ID" @@ -425,13 +398,11 @@ jobs: OUTPUT=$(yarn utils:check_balance "$SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS" "${{ steps.setup.outputs.lender_wallet_address }}" "$SOURCE_CHAIN_RPC_URL") echo "$OUTPUT" echo "$OUTPUT" | grep "Raw Balance: 5000000000000000050" - echo "$OUTPUT" | grep "Formatted Balance: 5.00000000000000005" echo "INFO: Borrower should have 3 tokens - interest" OUTPUT=$(yarn utils:check_balance "$SOURCE_CHAIN_ERC20_CONTRACT_ADDRESS" "${{ steps.setup.outputs.borrower_wallet_address }}" "$SOURCE_CHAIN_RPC_URL") echo "$OUTPUT" echo "$OUTPUT" | grep "Raw Balance: 2999999999999999950" - echo "$OUTPUT" | grep "Formatted Balance: 2.99999999999999995" - name: Upload logs if: always() diff --git a/loan/scripts/loan_proof.ts b/loan/scripts/loan_proof.ts index df4f053b..7511751b 100644 --- a/loan/scripts/loan_proof.ts +++ b/loan/scripts/loan_proof.ts @@ -1,4 +1,4 @@ -import { Contract, ethers } from 'ethers'; +import { Contract, ethers, JsonRpcApiProvider } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; import { @@ -9,6 +9,7 @@ import { submitFundProofToLoanManager, submitRepayProofToLoanManager, } from '../../shared/utils'; +import { proofProvider } from '@gluwa/usc-sdk'; /** Matches LoanStatus in LoanTypes.sol */ const LOAN_STATUS_CREATED = 0; @@ -23,6 +24,100 @@ async function getLoanStatus(managerContract: Contract, loanId: number): Promise return Number(order[6]); } +function logFundedSuccess(loanId: number): void { + console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); +} + +function logRepaidSuccess(loanId: number): void { + console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); +} + +function isFundedStatus(status: number): boolean { + return status === LOAN_STATUS_FUNDED || status === LOAN_STATUS_PARTLY_REPAID; +} + +async function submitProofExecuteWithRetry( + managerContract: Contract, + loanId: number, + kind: LoanProofKind, + proofData: proofProvider.ContinuityResponse, + ccProvider: JsonRpcApiProvider, + ccWalletAddress: string +): Promise { + const isRepayment = kind === 'repay'; + const maxAttempts = 3; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const status = await getLoanStatus(managerContract, loanId); + + if (kind === 'fund') { + if (status !== LOAN_STATUS_CREATED) { + if (isFundedStatus(status)) { + logFundedSuccess(loanId); + } + return; + } + } else if (status === LOAN_STATUS_REPAID) { + logRepaidSuccess(loanId); + return; + } else if (status !== LOAN_STATUS_FUNDED && status !== LOAN_STATUS_PARTLY_REPAID) { + throw new Error(`Loan ${loanId} is not in a repayable status on Creditcoin (status=${status})`); + } + + try { + const gasLimit = await computeGasLimitForLoanManager( + ccProvider, + managerContract, + proofData, + ccWalletAddress, + isRepayment + ); + + const response = isRepayment + ? await submitRepayProofToLoanManager(managerContract, proofData, gasLimit) + : await submitFundProofToLoanManager(managerContract, proofData, gasLimit); + + console.log(`Proof execute submitted for loan ${loanId}, tx hash: ${response.hash}`); + await response.wait(); + + if (kind === 'fund') { + console.log(`Marked loan ${loanId} as funded on Creditcoin, tx hash: ${response.hash}`); + logFundedSuccess(loanId); + return; + } + + console.log(`Marked loan ${loanId} as repaid on Creditcoin, tx hash: ${response.hash}`); + const newStatus = await getLoanStatus(managerContract, loanId); + if (newStatus === LOAN_STATUS_REPAID) { + logRepaidSuccess(loanId); + } else if (newStatus === LOAN_STATUS_PARTLY_REPAID) { + const order = await managerContract.getLoanOrder(loanId); + const repaidAmount = order[7]; + console.log(`Loan ${loanId} has been partially repaid on Creditcoin. Amount repaid: ${repaidAmount}`); + } + return; + } catch (error: any) { + const statusAfter = await getLoanStatus(managerContract, loanId); + + if (kind === 'fund' && isFundedStatus(statusAfter)) { + logFundedSuccess(loanId); + return; + } + if (kind === 'repay' && statusAfter === LOAN_STATUS_REPAID) { + logRepaidSuccess(loanId); + return; + } + + const message = error.shortMessage ?? error.message ?? String(error); + if (attempt === maxAttempts) { + throw new Error(`Proof execute failed after ${maxAttempts} attempts: ${message}`); + } + console.warn(`Proof execute attempt ${attempt} failed for loan ${loanId}: ${message}`); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } +} + /** * Prove a source-chain fund/repay tx and submit to ASCLoanManager on Creditcoin. * Uses the tx hash + proof builder (no wide eth_getLogs scans on Sepolia). @@ -66,14 +161,14 @@ export async function submitLoanProofAfterTx(loanId: number, txHash: string, kin console.log( `Loan ${loanId} is not in Created status on Creditcoin (status=${status}), skipping fund proof submission.` ); - if (status === LOAN_STATUS_FUNDED || status === LOAN_STATUS_PARTLY_REPAID) { - console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); + if (isFundedStatus(status)) { + logFundedSuccess(loanId); } return; } } else { if (status === LOAN_STATUS_REPAID) { - console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); + logRepaidSuccess(loanId); return; } if (status !== LOAN_STATUS_FUNDED && status !== LOAN_STATUS_PARTLY_REPAID) { @@ -91,56 +186,5 @@ export async function submitLoanProofAfterTx(loanId: number, txHash: string, kin throw new Error(`Failed to generate proof: ${proofResult.error}`); } - // Attestation can take several minutes; the off-chain worker may have submitted the proof while we waited. - const statusAfterProof = await getLoanStatus(managerContract, loanId); - - if (kind === 'fund') { - if (statusAfterProof !== LOAN_STATUS_CREATED) { - if (statusAfterProof === LOAN_STATUS_FUNDED || statusAfterProof === LOAN_STATUS_PARTLY_REPAID) { - console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); - } else { - console.log( - `Loan ${loanId} is no longer in Created status on Creditcoin after proof wait (status=${statusAfterProof}).` - ); - } - return; - } - } else if (statusAfterProof === LOAN_STATUS_REPAID) { - console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); - return; - } else if (statusAfterProof !== LOAN_STATUS_FUNDED && statusAfterProof !== LOAN_STATUS_PARTLY_REPAID) { - throw new Error(`Loan ${loanId} is not in a repayable status after proof wait (status=${statusAfterProof})`); - } - - const isRepayment = kind === 'repay'; - const gasLimit = await computeGasLimitForLoanManager( - ccProvider, - managerContract, - proofResult.data!, - ccWallet.address, - isRepayment - ); - - const response = isRepayment - ? await submitRepayProofToLoanManager(managerContract, proofResult.data!, gasLimit) - : await submitFundProofToLoanManager(managerContract, proofResult.data!, gasLimit); - - await response.wait?.(); - - if (kind === 'fund') { - console.log(`Marked loan ${loanId} as funded on Creditcoin, tx hash: ${response.hash}`); - console.log(`Loan ${loanId} has been marked as funded on Creditcoin.`); - return; - } - - console.log(`Marked loan ${loanId} as repaid on Creditcoin, tx hash: ${response.hash}`); - - const newStatus = await getLoanStatus(managerContract, loanId); - if (newStatus === LOAN_STATUS_REPAID) { - console.log(`Loan ${loanId} has been marked as fully repaid on Creditcoin.`); - } else if (newStatus === LOAN_STATUS_PARTLY_REPAID) { - const order = await managerContract.getLoanOrder(loanId); - const repaidAmount = order[7]; - console.log(`Loan ${loanId} has been partially repaid on Creditcoin. Amount repaid: ${repaidAmount}`); - } + await submitProofExecuteWithRetry(managerContract, loanId, kind, proofResult.data!, ccProvider, ccWallet.address); } diff --git a/loan/scripts/register.ts b/loan/scripts/register.ts index 6ec0afc4..29cc182e 100644 --- a/loan/scripts/register.ts +++ b/loan/scripts/register.ts @@ -1,6 +1,7 @@ import { Contract, ethers, EventLog } from 'ethers'; import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; +import loanHelperAbi from '../contracts/abi/AuxiliaryLoanContract.json'; import { isValidContractAddress, isValidPrivateKey } from '../../shared/utils'; import { loadEnv } from '../../shared/env'; @@ -131,7 +132,7 @@ const main = async () => { const lenderSignature = await lenderWallet.signMessage(ethers.toBeArray(payloadToSign)); const borrowerSignature = await borrowerWallet.signMessage(ethers.toBeArray(payloadToSign)); - let loanRegistered = false; + let loanId: bigint | null = null; const registerBlock = await ccProvider.getBlockNumber(); @@ -146,7 +147,7 @@ const main = async () => { } // 5. Wait for the LoanRegistered event - while (!loanRegistered) { + while (loanId === null) { const currentBlock = await ccProvider.getBlockNumber(); console.log(`Waiting for LoanRegistered event... Current block: ${currentBlock}`); const events = await managerContract.queryFilter( @@ -157,12 +158,42 @@ const main = async () => { if (events.length > 0) { const eventData = events[0] as EventLog; - const loanId = eventData.args.loanId; - console.log(`Loan successfully registered with ID: ${loanId.toString()}`); - loanRegistered = true; + loanId = eventData.args.loanId; + console.log(`Loan successfully registered with ID: ${eventData.args.loanId.toString()}`); } - await new Promise((resolve) => setTimeout(resolve, 5000)); + if (loanId === null) { + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } + + if (loanId === null) { + throw new Error('LoanRegistered event was not observed'); + } + + if (process.env.LOAN_REGISTER_SOURCE_FUNDING_IN_REGISTER === '1') { + const sourceChainRpcUrl = process.env.SOURCE_CHAIN_RPC_URL; + const sourceChainLoanContractAddress = process.env.SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS; + + if (!sourceChainRpcUrl) { + throw new Error('SOURCE_CHAIN_RPC_URL environment variable is not configured or invalid'); + } + if (!isValidContractAddress(sourceChainLoanContractAddress)) { + throw new Error('SOURCE_CHAIN_LOAN_CONTRACT_ADDRESS environment variable is not configured or invalid'); + } + + const sourceChainProvider = new ethers.JsonRpcProvider(sourceChainRpcUrl); + const sourceChainWallet = new ethers.Wallet(ccNextWalletPrivateKey!, sourceChainProvider); + const sourceChainLoanContract = new Contract(sourceChainLoanContractAddress!, loanHelperAbi, sourceChainWallet); + + const tx = await sourceChainLoanContract.registerLoanFund( + loanId, + fundFlow, + loanTerm.loanAmount, + loanTerm.expectedRepaymentAmount + ); + await tx.wait(); + console.log(`Registered loan ${loanId.toString()} for funding on source chain, tx hash: ${tx.hash}`); } process.exit(0); diff --git a/loan/scripts/wait_loan_status.ts b/loan/scripts/wait_loan_status.ts new file mode 100644 index 00000000..5e4a4364 --- /dev/null +++ b/loan/scripts/wait_loan_status.ts @@ -0,0 +1,86 @@ +import { Contract, ethers } from 'ethers'; + +import loanManagerAbi from '../contracts/abi/ASCLoanManager.json'; +import { isValidContractAddress } from '../../shared/utils'; +import { loadEnv } from '../../shared/env'; + +loadEnv('loan'); + +const STATUS_NAMES = ['Created', 'Funded', 'PartlyRepaid', 'Repaid', 'Expired'] as const; +type StatusName = (typeof STATUS_NAMES)[number]; + +function parseStatusName(name: string): number { + const index = STATUS_NAMES.indexOf(name as StatusName); + if (index < 0) { + throw new Error(`Invalid status "${name}". Expected one of: ${STATUS_NAMES.join(', ')}`); + } + return index; +} + +const main = async () => { + const args = process.argv.slice(2); + + if (args.length < 2 || args.length > 3) { + console.error(` + Usage: + yarn loan_flow:wait_loan_status [TimeoutSeconds] + + Example: + yarn loan_flow:wait_loan_status 1 Funded 1200 + `); + process.exit(1); + } + + const loanId = Number(args[0]); + const expectedStatus = parseStatusName(args[1]); + const timeoutSecs = args[2] ? Number(args[2]) : 1200; + + if (isNaN(loanId) || loanId < 0) { + throw new Error('Invalid Loan ID provided'); + } + if (isNaN(timeoutSecs) || timeoutSecs <= 0) { + throw new Error('Invalid timeout provided'); + } + + const ccNextRpcUrl = process.env.CREDITCOIN_RPC_URL; + const loanManagerContractAddress = process.env.ASC_LOAN_MANAGER_CONTRACT_ADDRESS; + + if (!ccNextRpcUrl) { + throw new Error('CREDITCOIN_RPC_URL environment variable is not configured or invalid'); + } + if (!isValidContractAddress(loanManagerContractAddress)) { + throw new Error('ASC_LOAN_MANAGER_CONTRACT_ADDRESS environment variable is not configured or invalid'); + } + + const ccProvider = new ethers.JsonRpcProvider(ccNextRpcUrl); + const managerContract = new Contract(loanManagerContractAddress!, loanManagerAbi, ccProvider); + + const deadline = Date.now() + timeoutSecs * 1000; + const expectedName = STATUS_NAMES[expectedStatus]; + + while (Date.now() < deadline) { + const currentOrder = await managerContract.getLoanOrder(loanId); + const status = Number(currentOrder[6]); + const statusName = STATUS_NAMES[status] ?? String(status); + + if (status === expectedStatus) { + console.log(`Loan ${loanId} reached status ${statusName} on Creditcoin.`); + process.exit(0); + } + + console.log(`Waiting for loan ${loanId} status ${expectedName} (current: ${statusName})...`); + await new Promise((resolve) => setTimeout(resolve, 15000)); + } + + const finalOrder = await managerContract.getLoanOrder(loanId); + const finalStatus = Number(finalOrder[6]); + console.error( + `Timed out after ${timeoutSecs}s waiting for loan ${loanId} status ${expectedName} (last status: ${STATUS_NAMES[finalStatus] ?? finalStatus}).` + ); + process.exit(1); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/package.json b/package.json index df14ac10..2f6f46b4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "loan_flow:fund_loan": "tsx loan/scripts/fund_loan.ts", "loan_flow:repay_loan": "tsx loan/scripts/repay_loan.ts", "loan_flow:inspect_loan": "tsx loan/scripts/inspect.ts", + "loan_flow:wait_loan_status": "tsx loan/scripts/wait_loan_status.ts", "loan_flow:start_worker": "tsx loan/scripts/worker.ts", "utils:check_balance": "tsx shared/utils/check_balance.js", "utils:check_setup": "tsx shared/check_setup.ts",