diff --git a/.gitmodules b/.gitmodules index 9852da38..2d562494 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "lib/bitcoin-spv"] path = lib/bitcoin-spv url = https://github.com/bob-collective/bitcoin-spv +[submodule "lib/account-abstraction"] + path = lib/account-abstraction + url = https://github.com/eth-infinitism/account-abstraction +[submodule "lib/gsn"] + path = lib/gsn + url = https://github.com/opengsn/gsn diff --git a/deploy-all.md b/deploy-all.md new file mode 100644 index 00000000..a4605793 --- /dev/null +++ b/deploy-all.md @@ -0,0 +1,105 @@ +# Steps to deploy all required contracts + +The steps below use various variables. In the latest deployments, these were set to: + +```sh +export PRIVATE_KEY= +export ENTRYPOINT_ADDRESS=0x8b57d6ec08e09078Db50F265729440713E024C6a +export RELAY_ADDRESS=0x7B72bA8c9f3Ba4A94E6d8fA07c822228034d2e61 +export ERC_ADDRESS=0x2868d708e442A6a940670d26100036d426F1e16b # wbtc +export ORACLE_ADDRESS=0x9AfBdFF0434acD4F325e3c35b739a62365099BCE # wbtc oracle +export ERC_DECIMALS=8 +export FORWARDER_ADDRESS=0xFd0042D3d05c82acb937aC86F23247a2D77785f2 +export RPC_URL=https://l2-puff-bob-jznbxtoq7h.t.conduit.xyz +export VERIFIER_URL='https://explorerl2new-puff-bob-jznbxtoq7h.t.conduit.xyz/api?' +export OWNER_ADDRESS='0x09Af4E864b84706fbCFE8679BF696e8c0B472201' +export BITCOIN_PRICE=3761500000000 +``` + +## GSN standard infrastructure + +Use the `gsn` tool to deploy the standard gsn infrasturcture. I believe the beta.10 version was used for the most recent deployment. + +```sh +npx gsn deploy --network https://l2-puff-bob-jznbxtoq7h.t.conduit.xyz --privateKeyHex $PRIVATE_KEY --testToken --burnAddress 0x09Af4E864b84706fbCFE8679BF696e8c0B472201 --devAddress 0x09Af4E864b84706fbCFE8679BF696e8c0B472201 +# Output: +# +# info: Setting minimum stake of 1 TestWeth on Hub +# info: Setting minimum stake of 0.000000000000000001 wnTok (0x14d8...446) +# Deployed GSN to network: https://l2-puff-bob-jznbxtoq7h.t.conduit.xyz +# +# RelayHub: 0x7B72bA8c9f3Ba4A94E6d8fA07c822228034d2e61 +# RelayRegistrar: 0x6Ff484e7530C4ab20aEa1B19E5b33FE7415dB9Fd +# StakeManager: 0xE5a27E68bE43A69dfd3A26be7DaE9Feac236C826 +# Penalizer: 0x1C36129916E3EA2ACcD516Ae92C8f91deF7c4146 +# Forwarder: 0xFd0042D3d05c82acb937aC86F23247a2D77785f2 +# TestToken (test only): 0x14d8b98c9f685FB3e13F5BB24B8016BD709A5446 +# Paymaster (Default): 0x0000000000000000000000000000000000000000 +``` + +## Miscelaneous deployments + +### Tokens + +```sh +# wbtc oracle: 0x9AfBdFF0434acD4F325e3c35b739a62365099BCE +# wbtc: 0x2868d708e442A6a940670d26100036d426F1e16b + +export ERC_PRICE=$BITCOIN_PRICE && forge script script/TestingWbtc.sol --rpc-url=$RPC_URL --broadcast --verify --verifier=blockscout --verifier-url=$VERIFIER_URL +``` + +### Marketplace +```sh +# btc marketplace: 0x0cfd830a59e94b6957609fFd85CcDD742C521F34 +# marketplace: 0x69F14d077Fcc88e70F4737a48fE09C0FD32506FB +# dummy relay: 0x077c5ed60fABb260784891786c6573373fDa8A3E +forge script script/Marketplace.sol --rpc-url=$RPC_URL --broadcast --verify --verifier blockscout --verifier-url=$VERIFIER_URL +``` + +### GSN Paymaster + +```sh +# paymaster: 0x25Aa86d188E37A47dd2011535534E53Cf994559d +forge script script/OracleTokenPaymaster.sol --rpc-url=$RPC_URL --broadcast --verify --verifier=blockscout --verifier-url=$VERIFIER_URL +``` + +# ERC-4337 + +## Entrypoint + +Clone [our fork](https://github.com/bob-collective/account-abstraction/tree/sepolia-bob) of the account-abstraction repo, set the `PRIVATE_KEY` environment variable, and deploy: + +```sh +yarn hardhat deploy --network sepoliaBob +``` + +The command above will output an address. Specify that address to verify the contract: +```sh +yarn hardhat verify --network sepoliaBob 0x8b57d6ec08e09078Db50F265729440713E024C6a +``` + + +## Deterministic deployer + +Clone [our fork](https://github.com/bob-collective/deterministic-deployment-proxy) of deterministic-deployment-proxy. This contract is used by the account abstraction wallets to deploy wallets to deterministic addresses. Edit the `chainIdNum` in the `scripts/compile.ts` file and then run `ts-node scripts/compile.ts`. Check the output as follows: + + +```sh +❯ cat output/deployment.json +{ + "gasPrice": 100000000000, + "gasLimit": 100000, + "signerAddress": "3760847f009a294e07309e80514ac0a7ee194269", + "transaction": "f8a78085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3820101a02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222", + "address": "250f60877f1397002ae79528b218be925b6b4c79" +} +``` + +Then, in the front-end code, update `DeterministicDeployer.ts` using the values from above. See https://github.com/bob-collective/demo-account-abstraction-transfer/pull/4 for an example. + + + +# Dummy oracle: 0x31b36BB047f6D5e3B49E95c4c99Cce4591e82E3f +# Dummy oracle: 0x6669d0C53fCf30c00F5AbE5a32cFa2EaD2bc2d5a +# Paymaster: 0x777FA19ea9e771018678161ABf2f1E2879D3cA6C +# forge script script/AATokenPaymaster.sol --rpc-url=$RPC_URL --broadcast --verify --verifier blockscout --verifier-url=$VERIFIER_URL \ No newline at end of file diff --git a/docs/docs/build/contracts/index.md b/docs/docs/build/contracts/index.md index 93b2d4ed..857c7dfd 100644 --- a/docs/docs/build/contracts/index.md +++ b/docs/docs/build/contracts/index.md @@ -25,7 +25,7 @@ - Deterministic deployer: `0x250f60877f1397002ae79528b218be925b6b4c79` - WBTC Paymaster: `0x777FA19ea9e771018678161ABf2f1E2879D3cA6C` -### Meta Tranactions (OpenGSN) +### Meta Transactions (OpenGSN) - RelayHub: `0x7B72bA8c9f3Ba4A94E6d8fA07c822228034d2e61` - RelayRegistrar: `0x6Ff484e7530C4ab20aEa1B19E5b33FE7415dB9Fd` diff --git a/docs/docs/build/examples/brc20-octopus/index.md b/docs/docs/build/examples/brc20-octopus/index.md index a0742523..7852d8fb 100644 --- a/docs/docs/build/examples/brc20-octopus/index.md +++ b/docs/docs/build/examples/brc20-octopus/index.md @@ -2,7 +2,7 @@ The BRC20 Octopus example demonstrates how the BOB SDK can be used to inscribe Ordinals, both text and BRC20s are supported. -The demo uses the [UniSat Wallet](https://docs.unisat.io/dev/unisat-wallet-api) to interact with the Bitcoin network and sign transactions. +The demo uses the [UniSat Wallet](https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet) to interact with the Bitcoin network and sign transactions. :::info Example Code @@ -16,6 +16,6 @@ With the app (and server) running locally you should see the following page: :::tip BOB SDK -To add this functionality to your app use the `inscribeText` function and provide a custom `RemoteSigner` implementation for your wallet. This needs to be able to send Bitcoin to the commit address and sign the PSBT of the reveal transaction. Check the test in [`sdk/test/ordinals.test.ts`](https://github.com/bob-collective/bob/blob/master/sdk/test/ordinals.test.ts) for an example implementation. +To add this functionality to your app use the `inscribeData` function and provide a custom `RemoteSigner` implementation for your wallet. This needs to be able to send Bitcoin to the commit address and sign the PSBT of the reveal transaction. Check the test in [`sdk/test/ordinals.test.ts`](https://github.com/bob-collective/bob/blob/master/sdk/test/ordinals.test.ts) for an example implementation. ::: diff --git a/docs/docs/build/examples/unified-assets-tracker/index.md b/docs/docs/build/examples/unified-assets-tracker/index.md index 7ab954b7..a41b727f 100644 --- a/docs/docs/build/examples/unified-assets-tracker/index.md +++ b/docs/docs/build/examples/unified-assets-tracker/index.md @@ -1,6 +1,12 @@ # Unified EVM and Bitcoin Assets with the MetaMask Bitcoin Snap Extension -This app allows tracking and transfering both EVM and Bitcoin assets in a unified manner using the [Zion MetaMask Bitcoin snap extension](https://snaps.metamask.io/snap/npm/btcsnap/). +:::info MetaMask Flask + +This demo uses [MetaMask Snaps](https://metamask.io/snaps/) and can only be used with [MetaMask Flask](https://metamask.io/flask/). MetaMask Snaps are not currently supported on mobile wallets, so this demo will only run in the desktop version of Chrome or Firefox. + +::: + +This app allows tracking and transferring both EVM and Bitcoin assets in a unified manner using the [BOB MetaMask snap extension](https://github.com/bob-collective/btcsnap). ![image](https://github.com/bob-collective/demo-unified-assets-tracker/assets/47864599/c13783e0-5cbe-4a30-89d7-3c12a39cb408) diff --git a/docs/docs/build/getting-started/index.md b/docs/docs/build/getting-started/index.md index ce00ffa9..4eeaf4e2 100644 --- a/docs/docs/build/getting-started/index.md +++ b/docs/docs/build/getting-started/index.md @@ -26,7 +26,7 @@ This is alpha-stage software. We love to work closely with you to make BOB usefu ## Examples -- [P2P Swap BTC and ERC20](/docs/build/examples/btc-swap/): Learn how to build a P2P Bitcoin marketplace on BOB using a BTC light client to eliminate trusted thrid parties. +- [P2P Swap BTC and ERC20](/docs/build/examples/btc-swap/): Learn how to build a P2P Bitcoin marketplace on BOB using a BTC light client to eliminate trusted third parties. - [Inscribing Ordinals With Unisat Wallet](/docs/build/examples/brc20-octopus/): Learn how to inscribe Ordinals (text, BRC20s, ...) with the UniSat wallet. - [Use MetaMask to Inscribe and Transfer Ordinals](/docs/build/examples/metamask-ordinals/): Learn how to inscribe Ordinals (text, BRC20s, ...) with the UniSat wallet. - [Unify BTC and EVM Assets](/docs/build/examples/unified-assets-tracker/): Learn how to unify BTC and EVM assets with a single wallet by using MetaMask snaps. diff --git a/docs/docs/build/how-to/bridged-btc-gas-fee/account-abstraction/index.md b/docs/docs/build/how-to/bridged-btc-gas-fee/account-abstraction/index.md index c05af364..08239fed 100644 --- a/docs/docs/build/how-to/bridged-btc-gas-fee/account-abstraction/index.md +++ b/docs/docs/build/how-to/bridged-btc-gas-fee/account-abstraction/index.md @@ -49,14 +49,14 @@ Before the first user operation can be made, the paymaster smart contract has to ## Limitations -Given that ERC-4337 is still relatively new, there is not a lot of support for this standard available yet on "traditiona" crypto wallets like MetaMask, Ledger, and others. +Given that ERC-4337 is still relatively new, there is not a lot of support for this standard available yet on "traditional" crypto wallets like MetaMask, Ledger, and others. ## Opportunities There are a interesting use cases that account abstraction enables being pushed by several team. Some places to look for inspiration: - [Privy](https://docs.privy.io/): Privy allows users to chose from traditional crypto wallets and social logins to create accounts. -- [Safe](https://docs.safe.global/getting-started/readme): Safe allows complex multi-sig setups, simple authetication, and onramping. +- [Safe](https://docs.safe.global/getting-started/readme): Safe allows complex multi-sig setups, simple authentication, and onramping. - [Pimlico](https://pimlico.notion.site/Product-Directory-5d92fe60243b4c5aac6650de390e7cb3): Pimlico tracks several products around account abstraction and it is worthwhile checking out the rapid and new developments in this space. ## Local development diff --git a/docs/docs/build/how-to/ordinals.md b/docs/docs/build/how-to/ordinals.md index fac66258..bfdfa56b 100644 --- a/docs/docs/build/how-to/ordinals.md +++ b/docs/docs/build/how-to/ordinals.md @@ -35,7 +35,7 @@ Check out the [documentation](../examples/metamask-ordinals/), the [BOB MetaMask - [Unisat](https://unisat.io/download) (Browser) ### Inscribing data -If you're developing an app with Javascript or Typescript you can use the BOB SDK to create the commit and reveal transactions required to inscribe data to an ordinal. Refer to the `inscribeText` function and provide a custom `RemoteSigner` implementation for your wallet. The test in [`sdk/test/ordinals.test.ts`](https://github.com/bob-collective/bob/blob/master/sdk/test/ordinals.test.ts) provides an example implementation using [`bitcoinjs-lib`](https://github.com/bitcoinjs/bitcoinjs-lib) and [`tiny-secp256k1`](https://github.com/bitcoinjs/tiny-secp256k1). +If you're developing an app with Javascript or Typescript you can use the BOB SDK to create the commit and reveal transactions required to inscribe data to an ordinal. Refer to the `inscribeData` function and provide a custom `RemoteSigner` implementation for your wallet. The test in [`sdk/test/ordinals.test.ts`](https://github.com/bob-collective/bob/blob/master/sdk/test/ordinals.test.ts) provides an example implementation using [`bitcoinjs-lib`](https://github.com/bitcoinjs/bitcoinjs-lib) and [`tiny-secp256k1`](https://github.com/bitcoinjs/tiny-secp256k1). #### Using an external service There are other ways to create inscriptions, the following two are advised: diff --git a/docs/docs/build/how-to/relay.md b/docs/docs/build/how-to/relay.md index 18857c07..bdb58a1a 100644 --- a/docs/docs/build/how-to/relay.md +++ b/docs/docs/build/how-to/relay.md @@ -79,7 +79,7 @@ This approach is still experimental and not yet fully supported by the SDK. To c ### Checking Output Amounts -To extract the output amount `BitcoinTx.getTxOutputValue` can be be used to extract the amount transfered to a specific address. See [`test/BitcoinTx.t.sol`](https://github.com/bob-collective/bob/blob/master/test/BitcoinTx.t.sol) for an example. The address is the `keccak256` hash of the expected `scriptPubKey`. +To extract the output amount `BitcoinTx.getTxOutputValue` can be be used to extract the amount transferred to a specific address. See [`test/BitcoinTx.t.sol`](https://github.com/bob-collective/bob/blob/master/test/BitcoinTx.t.sol) for an example. The address is the `keccak256` hash of the expected `scriptPubKey`. :::tip BOB SDK diff --git a/docs/docs/build/tools/api.md b/docs/docs/build/tools/api.md index 4b46c902..40bb6b7a 100644 --- a/docs/docs/build/tools/api.md +++ b/docs/docs/build/tools/api.md @@ -6,8 +6,8 @@ sidebar_position: 2 ## Ordinals -### [UniSat API](https://docs.unisat.io/dev/open-api) +### [UniSat API](https://docs.unisat.io/dev/unisat-developer-service) UniSat Open-API is open to community developers, allowing you to explore the world of Bitcoin and ordinals. -Getting an API key: [Contact the team](https://docs.unisat.io/dev/open-api#getting-an-api-key). \ No newline at end of file +Getting an API key: [Contact the team](https://docs.unisat.io/dev/unisat-developer-service#getting-an-api-key). \ No newline at end of file diff --git a/docs/docs/drafts/roadmap.md b/docs/docs/drafts/roadmap.md index 26a0b48c..ae1ab326 100644 --- a/docs/docs/drafts/roadmap.md +++ b/docs/docs/drafts/roadmap.md @@ -16,7 +16,7 @@ The core things that need building is: We already have key parts of the runtime built and live: -- Basic DeFi functions with a Uniswap v2, Curve v1, and Compound v2 are natively availabl ein the runtime. +- Basic DeFi functions with a Uniswap v2, Curve v1, and Compound v2 are natively available in the runtime. - Most decentralized Bitcoin bridge is live since August 2022. This includes a Bitcoin light client that is capable of trustless transaction inclusion proofs of Bitcoin transaction. - Access to native USDT, USDC (soon), and cross-chain assets via Wormhole (soon). - Fully decentralized governance with upgradable runtimes. @@ -40,7 +40,7 @@ We already have key parts of the runtime built and live: - Hackathon templates - OP Stack compatibility - Data availability layer support/relayer - - Sequencing implmentation + - Sequencing implementation - Execution layer - Settlement layer - Bitcoin compatibility diff --git a/docs/docs/drafts/stack.md b/docs/docs/drafts/stack.md deleted file mode 100644 index 41311b0a..00000000 --- a/docs/docs/drafts/stack.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -sidebar_position: 3 -hide: true ---- -# The BOB Stack - -As BOB is compatible with the OP Stack, it uses the [six OP Stack layers](https://stack.optimism.io/docs/understand/landscape/) and adds one layer for Bitcoin interoperability. - -BOB has the following stack: - -1. Governance: on-chain governance is used to upgrade and change the BOB stack. -2. Settlement layer: the settlement layer is the L1 from which BOB receives its finality and consensus security. BOB uses Ethereum for settlement with the long-term vision to rollup against Bitcoin once suitable OP codes are available. -3. Execution layer: The execution layer is implemented in substrate and exposes three domains for state transition functions: - 1. Core runtime: implemented in Rust and substrate as a governance-controlled upgradeable runtime that houses a BTC light client, DeFi functions, governance, and more. - 2. Rust smart contracts: implemented in Rust and the ink! eDSL as a way for Bitcoin smart contract engineers to leverage the power of `rust-bitcoin` and other libraries to implement immutable smart contracts interacting with the core runtime and the EVM. - 3. EVM smart contracts: implemented through the frontier pallet, a runtime to execute EVM smart contracts that can interact with the core runtime and the Rust smart contracts to support EVM wallets and tooling (Safe, Fireblocks, Etherscan, …) -4. Derivation layer: -5. Sequencing layer: -6. Data availability layer: The data availability layer stores inputs to the execution layer. BOB is yet to decide which DA layer to use among several options iuncluding Bitcoin, Ethereum, or Celestia. -7. Bitcoin layer: The Bitcoin layer provides access to BTC and other assets from the Bitcoin chain as well as data reads from Bitcoin via alight client. BOB uses a novel bridge design where minters create a synthetic Bitcoin on BOB that can be swapped cross-chain with BTC without trusted intermediaries. - -## Data Availability - -A Data Availability (DA) layer stores the raw inputs to the state transition function for the execution layer. The OP Stack supports [multiple DAs but sets Ethereum as the de-facto DA](https://stack.optimism.io/docs/understand/landscape/#data-availability). The OP Stack Superchain is considering a dedicated [Plasma DA chain](https://stack.optimism.io/docs/understand/explainer/#alt-data-availability-layer-plasma-protocol) to address rising cost and limited scalability of Ethereum as a DA layer. - -BOB is considering the following DA layers: - -- Bitcoin: The Celestia team has a specification for [using Bitcoin as a DA layer](https://github.com/rollkit/bitcoin-da/blob/main/spec.md) that suits BOB needs and would ensure compatibility with Celestia deployments. -- Ethereum: OP Stack uses Ethereum as a DA [with a dedicated specification](https://github.com/ethereum-optimism/optimism/blob/129032f15b76b0d2a940443a39433de931a97a44/specs/derivation.md#batch-submission-wire-format). -- Others: There are other DA options like Celestia. - -## Sequencing - -The OP Stack uses a single, centralized sequencer. - -OPEN QUESTIONS -- Should we stick with a PoA-style sequencer as well? We could use AURA for that. -- Should we keep GRANDPA and BABE consensus style block production and finalization? This will likely require some custom implmenetation on the Settlement layer. - -## Execution - -OP Stack uses a geth fork for its EVM state and state transition functions. Instead of using op-geth directly, BOB's execution layer is implemented such that a state transition is applied as follows: - -1. Initial state: The initial state is retrieved from an EVM and OP Stack compatible Patricia Merkle trie stored in the DA layer. The Frontier pallet in substrate already implements decoding of EVM-compatible state. Having the initial state in an EVM/OP Stack-compatible trie (i.e., encoding and decoding of the trie is possible via Ethereum defined RPCs) allows wallets, block explorers, and other EVM-compatible tooling to read from this state. -2. State transition: State transitions are applied through a Substrate-based runtime implementation. The key difference to vanilla OP Stack is that instead of relying on the EVM core runtime and EVM smart contracts, the Substrate-based runtime is implemented in WASM and houses three distinct state transition domains. A state transition is achieved by the successful execution of a state transition function on either of the three domains. - a. Core runtime: The core runtime exposes functions that are implemented in Rust/Substrate and can be upgraded via governance. The are the equivalent of pre-compiles in the Ethereumm world. BOB exposes several core functions that builders can make use of including a Bitcoin light client, DeFi (AMM and lending), a Bitcoin bridge, transaction fee converters, and many more. Deployment of new core runtime functions is subject to governance vote. - b. Rust smart contracts: The core runtime has a dedicated space for builders to deploy arbitrary smart contracts written in Rust using an eDSL called ink!. Through an SDK, developers can write smart contracts within macros that can utilize the types from `rust-bitcoin`. Deploying Rust smart contracts is permissionless and deployed contracts are immutable. - c. EVM smart contracts: The core runtime further has a dedicated space for builders to deploy EVM-compatible contracts that can be written in Solidity or other languages that compile to the EVM. Deploying EVM smart contracts is permissionless and deployed contracts are immutable. -3. Resulting state: Upon successful completion of the state transition function, the state stored in the Patricia Merkle trie is updated as indicated in the deterministic state transition function. The state is then updated and stored in the DA layer. - -## Settlement - -OP Stack settles on Ethereum and uses fraud proofs. While BOB is made for Bitcoin, there are distinct reasons where BOB will initially not roll up against Bitcoin: - -1. Bitcoin's consensus only validates BTC as an asset. By rolling up against Ethereum, users can exit Ethereum-native assets (those that are validted by Ethereum consensus). This requires a bridge to Bitcoin, but having a fully collaterlaized Bitcoin bridge offers similar security levels as a *threoretically* possible roll-up to Bitcoin. -2. Bitcoin lacks the possibility have its consensus validate a roll-up at the moment. While the BOB project closely follows developments areound adding an `OP_ZKPVERIFY`, it remains quesitonable when such a code will be available. - -In the future, BOB can also expose capabilities to be ZK-compatible where the data structure is rather compatible with Starkware instead of the OP Stack to enable a ZKP roll-up on Bitcoin and ZKP bridges to Ethereum and other EVM networks. - -## Bridge - -...add details about bridge here. - diff --git a/docs/docs/drafts/vision.md b/docs/docs/drafts/vision.md new file mode 100644 index 00000000..5b4d99b7 --- /dev/null +++ b/docs/docs/drafts/vision.md @@ -0,0 +1,80 @@ +--- +sidebar_position: 1 +--- +# Technical Vision + +We see BOB as a collective undertaking to scale Bitcoin the right way: inheriting security from Bitcoin while providing usable decentralization for builders today without waiting for hard forks. + +The technical vision outlines the endgame for BOB and describes how it differs from the first deployments. The discrepancy between endgame and current state forms opportunities to contribute and push the BOB stack further. + +## Bringing Bitcoin Security to Rollups + +We believe that rollups and sidechains should receive their consensus security from Bitcoin rather than relying on other L1s. + +### Ideal World: Verifiably Secure through Zero Knowledge Proofs + +To inherit full Bitcoin security, Bitcoin miners need to validate a BOB rollup state transition as part of Bitcoin consensus such that the rollup state transition function becomes part of the Bitcoin state transition function. No current Bitcoin sidechain achieves this level of security as this will require a hard fork of Bitcoin. + +In our opinion, non-interactive zero-knowledge proofs (NIZKP) are the ideal candidates to achieve full Bitcoin security as the verification of the proof is much simpler than its creation. The creation of the proof can be done by the rollup nodes. Bitcoin would need to add an OP code to verify ZK proofs or there would need to be great improvements in the efficiency of [BitVM](https://bitvm.org) to be able to verify ZK proofs. + +However, adding a ZK verification OP code and being able to verify ZK proofs in BitVM will likely take years as there is still heavy changes around ZK proofs and no "gold standard" has yet emerged. + +### Hardfork-free Bitcoin Security through Merged Mining + +As we expect that verifying ZK proofs is still a long way to go, the next best thing to inherit Bitcoin security for BOB rollups is to allow miners to verify rollup state transitions. Miners can opt-in to verify rollups and if all miners were to merge mine a rollup, it would have equivalent PoW security as Bitcoin itself. + +When using Ethereum rollups, Ethereum becomes a co-processor to Bitcoin where the computation happens on the EVM rollup but the verification is done by the miner. + +We see a staged approach to make use of the rollup PoW: + +- **Stage 1**: Dapps deployed on an EVM rollup can check the submitted PoW to the rollup and customize their interpretation. For example, a ordinals P2P exchange might want to pause their platform if insufficient PoW is attached to the rollup, Other dapps might ignore the PoW alltogether, making the model quite flexible. +- **Stage 2**: Merged minig becomes a condition for a valid state transition of the rollup. Assuming that a rollup is launched on an L1 with smart contracts like Ethereum, the sequencer needs to ensure that at certain intervals a sufficient PoW is added. The rollup would be paused and its state could be invalidated through including the PoW as part of the fraud proofs (in optimistic rollups) and validity proofs (in zk rollups). Requiring PoW as part of a valid state transition for the rollup ensures that the state transition of the rollup on say Ethereum cannot settle without the explicit consent from Bitcoin miners. +- **Stage 3**: Staking sequencers on Bitcoin and proving incorrect behavior through BitVM, one-time signatures, and other techniques ensures that sequencers are ecnomically incentivized on Bitcoin to correctly produce L2 blocks. +- **Stage 4**: In the final stage, the rollup transitions to a fully zk-verified rollup that can then be verified by Bitcoin consensus without merged mining. We expect this to take about five years from now as it will require zk technology to mature and Bitcoin made capable of verifying zk proofs. + +:::info +We will share a technical paper on optimistic sequenced merged mining detailing the technical protocol soon. +::: + +:::note BOB Launch PLan +BOB launches as an [optimistic rollup using the OP Stack](/docs/learn/networks/op-stack.md) which may seem counterintuitive to the above goal of eventually being a ZK rollup. However, we see having full EVM compatibility and tooling a worthwhile trade-off to adopting zkEVM rollups. Moreover, we see promising progress around abstracting the entire EVM execution into higher level zkVM like Risc Zero. Executing entire rollup blocks in a zkVM requires no changes to the EVM while still allowing validity proof production that then can eventually be used for a Bitcoin-verified ZK rollup. + +We further will launch the merged mining option shortly after BOB will go live in the stage 1 option described above. +::: + +## Usable Decentralization + +Centralization is plaguing development on Bitcoin today. Due to Bitcoin's limited programmability, many applications building on Bitcoin, like the majority of Lightning wallets, are centralized as it allows for a better UX than their dencentralized counterparts. + +Lack of decentalized appliocations with great UX is a major issue that can be resolved by (1) allowing more epxressive smart contracts and piggy-backing off of the developments made on Ethereum and other L1 chains, and (2) ensuring that the EVM rollups are still secured by Bitcoin. + +### Adoption Through UX + +- Readily available assets for Bitcoin users by native bridged (rollups) to Ethereum +- Unified UX of Bitcoin with BOB-enhanced rollups will win out +- Mass adoption will need privacy + +### Multi-Chain and Multi-Rollup Future + +- BOB is not a single rollup: different rollups can make different trade-offs for various use cases. Examples: very fast block times with limited contracting for payments, large blocks for storing ordinals, ... +- BOB is an enhancer of existing sidechains and rollups and can be added to existing EVM chains + +### Off-chain Computation before On-chain Computation + +- Even in the EVM, some computations/programs might still be too complex +- Complex programs like a BRC20 or Ordinals co-processor should be operated off-chain and its correct execution proven on-chain +- Simple programs can be kept on-chain + +## Briding BTC, Ordinals, and BRC20s + +We believe that in some cases, bridging BTC, Ordinals, and BRC20s to more chains with higher programmability than Bitcoin is required. + +### Bitcoin Bridges without Trusted Parties + +- In theory, if Bitcoin could verify the consensus of another chain, it would be possible to build a bridge that would only rely on the Bitcoin and other chains consensus security plus untrusted block relayers +- In practice, verifying another chains consensus is not possible on Bitcoin and therefore we have to find workarounds +- Add details about different bridge models + +:::note BOB Launch Plan +BOB will launch with the tBTC bridge as it provides a good trade-off that features a 1:1 peg, a distribution of trust among many parties through their threshold signatures, and partial collateralization. +::: \ No newline at end of file diff --git a/docs/docs/learn/rollup/_category_.yml b/docs/docs/learn/guides/_category_.yml similarity index 70% rename from docs/docs/learn/rollup/_category_.yml rename to docs/docs/learn/guides/_category_.yml index 6fac012b..0b9efbb4 100644 --- a/docs/docs/learn/rollup/_category_.yml +++ b/docs/docs/learn/guides/_category_.yml @@ -1,4 +1,4 @@ position: 2 -label: 'Rollup' +label: 'Using BOB' collapsible: true collapsed: true \ No newline at end of file diff --git a/docs/docs/learn/guides/index.md b/docs/docs/learn/guides/index.md new file mode 100644 index 00000000..95b9f8de --- /dev/null +++ b/docs/docs/learn/guides/index.md @@ -0,0 +1,43 @@ +# Using BOB + +## P2P Swap Demo + +The P2P swap demo shows how BOB enables swaps without bridging between BTC, ordinals, and ERC20 assets. + +- [P2P Swap Demo](https://demo.gobob.xyz/) +- [User Guide](/docs/build/examples/btc-swap/#demo) + +## BOB Testnet + +If you want to use the testnet to interact with other apps deployed on BOB or deploy your own contracts, please follow the following steps: + +1. Install an EVM wallet such as [Metamask](https://metamask.io/). +2. Add the BOB testnet to MetaMask or other wallet: + 1. Open the MetaMask browser extension. + 2. Open the network selection dropdown menu by clicking the dropdown button at the top of the extension. + 3. Click the Add network button. + 4. Click Add a network manually. + 5. In the Add a network manually dialog that appears, enter the following information: + - Network Name: BOB Testnet + - New RPC URL: https://testnet.rpc.gobob.xyz + - Chain ID: 111 + - Currency Symbol (optional): ETH + - Block Explorer URL (optional): https://testnet-explorer.gobob.xyz/ + 6. Click Save. +3. Get Sepolia ETH from one the faucets: https://faucetlink.to/sepolia +4. Connect your wallet to the Sepolia BOB bridge and bridge your ETH to the BOB testnet: + - [BOB Bridge](https://testnet-bridge.gobob.xyz/) + - [Superbridge.app](https://puff-bob-jznbxtoq7h.testnets.superbridge.app/) +5. You are now ready to use the BOB testnet + +## Bitcoin Testnet + +If you want to connect BOB apps with Bitcoin testnet, please follow the steps below: + +1. Install a Bitcoin wallet such as [Leather](https://leather.io/), [Xverse](https://www.xverse.app/), or [UniSat](https://unisat.io/). For a complete list of wallets, see [Bitcoin.org](https://bitcoin.org/en/choose-your-wallet). +2. Get some Bitcoin testnet coins from a faucet: + - https://bitcoinfaucet.uo1.net/ + - https://coinfaucet.eu/en/btc-testnet/ + - https://kuttler.eu/en/bitcoin/btc/faucet/ + - https://tbtc.bitaps.com/ +3. You are now ready to use the Bitcoin testnet \ No newline at end of file diff --git a/docs/docs/learn/introduction/_category_.yml b/docs/docs/learn/introduction/_category_.yml index 32e32693..502635d2 100644 --- a/docs/docs/learn/introduction/_category_.yml +++ b/docs/docs/learn/introduction/_category_.yml @@ -1,4 +1,4 @@ position: 1 label: 'Introduction' collapsible: true -collapsed: true \ No newline at end of file +collapsed: false \ No newline at end of file diff --git a/docs/docs/learn/introduction/bob-components.excalidraw b/docs/docs/learn/introduction/bob-components.excalidraw deleted file mode 100644 index d1bc12b0..00000000 --- a/docs/docs/learn/introduction/bob-components.excalidraw +++ /dev/null @@ -1,416 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "id": "WpQRmBQrZbLK35c5dKsdG", - "type": "rectangle", - "x": 499.83203125, - "y": 245.96484375, - "width": 309.765625, - "height": 158.8203125, - "angle": 0, - "strokeColor": "#f08c00", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "seed": 1838345696, - "version": 34, - "versionNonce": 149889056, - "isDeleted": false, - "boundElements": null, - "updated": 1693285941352, - "link": null, - "locked": false - }, - { - "id": "ShGCZa6eRQQDpQ17I0Ooi", - "type": "text", - "x": 530, - "y": 215.796875, - "width": 158.7039337158203, - "height": 35, - "angle": 0, - "strokeColor": "#f08c00", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 887060960, - "version": 17, - "versionNonce": 1811410400, - "isDeleted": false, - "boundElements": null, - "updated": 1693286203982, - "link": null, - "locked": false, - "text": "BTC Bridge", - "fontSize": 28, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 25, - "containerId": null, - "originalText": "BTC Bridge", - "lineHeight": 1.25 - }, - { - "id": "fbbCNG_bYyIsmLkZf1J62", - "type": "text", - "x": 510.796875, - "y": 269.59375, - "width": 258.9398193359375, - "height": 125, - "angle": 0, - "strokeColor": "#f08c00", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 765718560, - "version": 174, - "versionNonce": 825295904, - "isDeleted": false, - "boundElements": null, - "updated": 1693286075031, - "link": null, - "locked": false, - "text": "- Deploy on any EVM chain\n- Synthetically mint\n collateralized BTC\n- Swap P2P for BTC on \n Bitcoin", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 118, - "containerId": null, - "originalText": "- Deploy on any EVM chain\n- Synthetically mint\n collateralized BTC\n- Swap P2P for BTC on \n Bitcoin", - "lineHeight": 1.25 - }, - { - "type": "rectangle", - "version": 71, - "versionNonce": 784447968, - "isDeleted": false, - "id": "byWWUb866C6Og_IfTY59m", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 982.828125, - "y": 238.30859375, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 309.765625, - "height": 158.8203125, - "seed": 1145905184, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [], - "updated": 1693286092181, - "link": null, - "locked": false - }, - { - "id": "qhDuHSDy_W_O8I7Vdai7u", - "type": "text", - "x": 993.1484375, - "y": 205.9140625, - "width": 307.6358947753906, - "height": 35, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 351784992, - "version": 86, - "versionNonce": 1840131552, - "isDeleted": false, - "boundElements": null, - "updated": 1693286103922, - "link": null, - "locked": false, - "text": "Rust smart contracts", - "fontSize": 28, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 25, - "containerId": null, - "originalText": "Rust smart contracts", - "lineHeight": 1.25 - }, - { - "id": "wCO8uCaGR0vgdcsom2j7r", - "type": "text", - "x": 993, - "y": 269, - "width": 302.17974853515625, - "height": 100, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 1352938976, - "version": 143, - "versionNonce": 859214304, - "isDeleted": false, - "boundElements": null, - "updated": 1693286149593, - "link": null, - "locked": false, - "text": "- Write Rust programs\n- Use libs like rust-bitcoin\n- Write zk off-chain programs\n in Rust with on-chain verifier", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 93, - "containerId": null, - "originalText": "- Write Rust programs\n- Use libs like rust-bitcoin\n- Write zk off-chain programs\n in Rust with on-chain verifier", - "lineHeight": 1.25 - }, - { - "id": "M3Ocj1nyNFqQFdHC1XfZF", - "type": "rectangle", - "x": 497.578125, - "y": 454.98828125, - "width": 783.93359375, - "height": 100.04296875, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "seed": 373904864, - "version": 121, - "versionNonce": 648195552, - "isDeleted": false, - "boundElements": null, - "updated": 1693286287791, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 171, - "versionNonce": 1273329696, - "isDeleted": false, - "id": "hoLEm172scB10dWUq8zM7", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 813.3472518920898, - "y": 413.8203125, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 125.719970703125, - "height": 35, - "seed": 1719611872, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1693286218255, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "EVM core", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "EVM core", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "id": "el-SUO0Bt-pMoE0O1hFYW", - "type": "text", - "x": 520, - "y": 487, - "width": 650.2195434570312, - "height": 50, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 683536416, - "version": 133, - "versionNonce": 793208864, - "isDeleted": false, - "boundElements": null, - "updated": 1693286282058, - "link": null, - "locked": false, - "text": "- Deploy any existing Solidity/EVM code base\n- Support account abstraction, EVM wallets, hardware wallets, ...", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 43, - "containerId": null, - "originalText": "- Deploy any existing Solidity/EVM code base\n- Support account abstraction, EVM wallets, hardware wallets, ...", - "lineHeight": 1.25 - }, - { - "type": "rectangle", - "version": 252, - "versionNonce": 1223497760, - "isDeleted": false, - "id": "-WlmE4nfNLhIJlg0FONgH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 497.154296875, - "y": 606.603515625, - "strokeColor": "#e03131", - "backgroundColor": "transparent", - "width": 783.93359375, - "height": 100.04296875, - "seed": 2110910944, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [], - "updated": 1693286298844, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 274, - "versionNonce": 1578538464, - "isDeleted": false, - "id": "aaTHGTX8y1Q61QHEsnB0X", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 835.4212646484375, - "y": 566.0625, - "strokeColor": "#e03131", - "backgroundColor": "transparent", - "width": 78.93196105957031, - "height": 35, - "seed": 718126560, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1693286312248, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Rollup", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Rollup", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "id": "LLZWluNzdshpk9uN_UP-f", - "type": "text", - "x": 510, - "y": 634, - "width": 638.41943359375, - "height": 50, - "angle": 0, - "strokeColor": "#e03131", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 1074961888, - "version": 99, - "versionNonce": 1534370272, - "isDeleted": false, - "boundElements": null, - "updated": 1693286342460, - "link": null, - "locked": false, - "text": "- Access to Ethereum assets\n- Compatibility with emerging ecosystems (OP Stack, Arbitrum, ...)", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 43, - "containerId": null, - "originalText": "- Access to Ethereum assets\n- Compatibility with emerging ecosystems (OP Stack, Arbitrum, ...)", - "lineHeight": 1.25 - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - }, - "files": {} -} \ No newline at end of file diff --git a/docs/docs/learn/introduction/bob-components.png b/docs/docs/learn/introduction/bob-components.png deleted file mode 100644 index b8c09b80..00000000 Binary files a/docs/docs/learn/introduction/bob-components.png and /dev/null differ diff --git a/docs/docs/learn/introduction/bob-stack.excalidraw b/docs/docs/learn/introduction/bob-stack.excalidraw new file mode 100644 index 00000000..4c21e3c9 --- /dev/null +++ b/docs/docs/learn/introduction/bob-stack.excalidraw @@ -0,0 +1,517 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "09tCDbgTjZdAuMK9P26rj", + "type": "rectangle", + "x": 551, + "y": -17, + "width": 860.9999999999999, + "height": 113.00000000000001, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 197851263, + "version": 95, + "versionNonce": 384634746, + "isDeleted": false, + "boundElements": null, + "updated": 1702535184287, + "link": null, + "locked": false + }, + { + "id": "cB8RQ-A9tK0QxuaKl9NMC", + "type": "text", + "x": 593, + "y": -12.5, + "width": 131.0500030517578, + "height": 35, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1716755391, + "version": 48, + "versionNonce": 84497018, + "isDeleted": false, + "boundElements": null, + "updated": 1702535168304, + "link": null, + "locked": false, + "text": "BOB SDK", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 25, + "containerId": null, + "originalText": "BOB SDK", + "lineHeight": 1.25 + }, + { + "id": "D2S3eTSe1QF3_uRG7UwnC", + "type": "text", + "x": 591, + "y": 22.5, + "width": 807.7333374023438, + "height": 60, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1351902239, + "version": 353, + "versionNonce": 736949050, + "isDeleted": false, + "boundElements": null, + "updated": 1702535168304, + "link": null, + "locked": false, + "text": "An SDK to make working with Bitcoin from the EVM a breeze. Comes with a MetaMask Snap to\ntransfer BTC, ordinals, and BRC20s. Includes a BTC light client, methods for working with BRC20s, \nordinals, and more.", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 55, + "containerId": null, + "originalText": "An SDK to make working with Bitcoin from the EVM a breeze. Comes with a MetaMask Snap to\ntransfer BTC, ordinals, and BRC20s. Includes a BTC light client, methods for working with BRC20s, \nordinals, and more.", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 356, + "versionNonce": 356507046, + "isDeleted": false, + "id": "_m9RC5xeuyEbHUQ3nPg5O", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 552.1333312988281, + "y": 111.5, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 434.00000000000006, + "height": 113.00000000000001, + "seed": 741151718, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1702535202321, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 227, + "versionNonce": 1822485690, + "isDeleted": false, + "id": "_947Kyqmki97QcjoEjbMp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 594.1333312988281, + "y": 116, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 204.0833282470703, + "height": 35, + "seed": 846047014, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535168304, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Bitcoin Bridges", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Bitcoin Bridges", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 577, + "versionNonce": 1779910010, + "isDeleted": false, + "id": "jMVc1Qz6X2UkiStAfX5DN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 592.1333312988281, + "y": 151, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 375.6666564941406, + "height": 18.999999999999993, + "seed": 565826150, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535168304, + "link": null, + "locked": false, + "fontSize": 15.199999999999994, + "fontFamily": 1, + "text": "Wrap Bitcoin, BRC20s, swap ordinals, stake BTC.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Wrap Bitcoin, BRC20s, swap ordinals, stake BTC.", + "lineHeight": 1.25, + "baseline": 14 + }, + { + "type": "rectangle", + "version": 425, + "versionNonce": 2105403046, + "isDeleted": false, + "id": "MlaxOJD62gPGuFQ29VWTb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1003.5, + "y": 108.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 409.99999999999994, + "height": 113.00000000000001, + "seed": 1205776678, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1702535198905, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 299, + "versionNonce": 1338790118, + "isDeleted": false, + "id": "MGmy-e2MNWK9vCBiaRgQm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1046.5, + "y": 113, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 145.48333740234375, + "height": 35, + "seed": 947493990, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535193610, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Rust zkVM", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Rust zkVM", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 728, + "versionNonce": 2031961126, + "isDeleted": false, + "id": "BX-15fnUNThEZloAldSFz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1044.5, + "y": 148, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 332, + "height": 37.999999999999986, + "seed": 85270438, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535193610, + "link": null, + "locked": false, + "fontSize": 15.199999999999994, + "fontFamily": 1, + "text": "Off-chain programs with on-chain verification\nand support for Rust Bitcoin libraries.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Off-chain programs with on-chain verification\nand support for Rust Bitcoin libraries.", + "lineHeight": 1.25, + "baseline": 33 + }, + { + "type": "rectangle", + "version": 158, + "versionNonce": 1858822394, + "isDeleted": false, + "id": "ZQEmT84ednziEaoIhAOds", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 554.1333312988281, + "y": 240.5, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 859, + "height": 113.00000000000001, + "seed": 1161256378, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1702535175771, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 121, + "versionNonce": 1606064442, + "isDeleted": false, + "id": "cfxbc-I9HtMXkFPO20k0K", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 596.1333312988281, + "y": 245, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 129.6999969482422, + "height": 35, + "seed": 683120250, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535168304, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "EVM Core", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "EVM Core", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 736, + "versionNonce": 373776890, + "isDeleted": false, + "id": "dOTf98quu1TiW6-PlLm5j", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 594.1333312988281, + "y": 280, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 812.0999755859375, + "height": 60, + "seed": 1228060474, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535168304, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Access to all the current EVM tooling like Remix, Foundary, Hardhat, OpenZeppelin, Safe, oracles, and\nmany more. Plus all the innovation happening for Account Abstraction, Meta Transaction, parallel EVM,\nscaling technologies, and a vast ecosystem of innovations.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Access to all the current EVM tooling like Remix, Foundary, Hardhat, OpenZeppelin, Safe, oracles, and\nmany more. Plus all the innovation happening for Account Abstraction, Meta Transaction, parallel EVM,\nscaling technologies, and a vast ecosystem of innovations.", + "lineHeight": 1.25, + "baseline": 55 + }, + { + "type": "rectangle", + "version": 223, + "versionNonce": 1227815418, + "isDeleted": false, + "id": "7987Z1nGzD2bMnZFK5Xml", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 557.1333312988281, + "y": 369.5, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 856.0000000000001, + "height": 113.00000000000001, + "seed": 1797439782, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1702535179321, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 200, + "versionNonce": 750693242, + "isDeleted": false, + "id": "owjPPlPgOLQqGnh6LeK5c", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 599.1333312988281, + "y": 375, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 168.86666870117188, + "height": 35, + "seed": 2025565286, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535168304, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Rollup Layer", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Rollup Layer", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 735, + "versionNonce": 1167010874, + "isDeleted": false, + "id": "2ZVMEMek_QgCmnUQV6iuU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 597.1333312988281, + "y": 410, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 820.4166870117188, + "height": 40, + "seed": 311766950, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1702535168304, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Compatible with any rollup stack. Native ETH assets. Bitcoin security through optimistic merged mining. \nStarting as an OP rollup with a path to become a zk-verified rollup against Ethereum and Bitcoin.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Compatible with any rollup stack. Native ETH assets. Bitcoin security through optimistic merged mining. \nStarting as an OP rollup with a path to become a zk-verified rollup against Ethereum and Bitcoin.", + "lineHeight": 1.25, + "baseline": 35 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/docs/learn/introduction/bob-stack.png b/docs/docs/learn/introduction/bob-stack.png new file mode 100644 index 00000000..ebeefe5f Binary files /dev/null and b/docs/docs/learn/introduction/bob-stack.png differ diff --git a/docs/docs/learn/introduction/contribution.md b/docs/docs/learn/introduction/contribution.md index b9b44caf..3fb657a3 100644 --- a/docs/docs/learn/introduction/contribution.md +++ b/docs/docs/learn/introduction/contribution.md @@ -2,10 +2,22 @@ We are just getting started with BOB and there are plenty of new things to be built, researched, and experimented with. The BOB collective is strengthened by each contributor. -## How to Contribute +## Community -The best way to get started is to: +If you want to engage with the community and learn how you can contribute, here are some resources: -- Checkout the GitHub: [bob-collective](https://github.com/bob-collective/bob) -- Try the early alpha: [builder docs](/docs/build/getting-started/helloworld) - Join the [Discord](https://discordapp.com/invite/interlay) +- Follow us on [Twitter](https://twitter.com/build_on_bob) +- Join the [forum](https://forum.gobob.xyz/) to discuss ideas and share feedback +- Try out the [P2P Swap](https://demo.gobob.xyz/) to see BOB in action +- Keep an eye on the growing [Ecosystem](https://www.gobob.xyz/) + +## Builders + +If you are keen to build on BOB or contribute to BOB itself: + +- Discuss ideas and share feedback on the [forum](https://forum.gobob.xyz/) +- Join the [Dev Telegram](https://t.me/+CyIcLW2nfaFlNDc1) +- Checkout the [bob-collective GitHub](https://github.com/bob-collective/bob) +- Deploy your first contracts on BOB: [Hello World](/docs/build/getting-started/helloworld) +- Try out the demos: [Demos](/docs/build/getting-started/#examples) diff --git a/docs/docs/learn/introduction/ideal-l2.md b/docs/docs/learn/introduction/ideal-l2.md index 6c172644..fc6b1f07 100644 --- a/docs/docs/learn/introduction/ideal-l2.md +++ b/docs/docs/learn/introduction/ideal-l2.md @@ -1,6 +1,6 @@ --- sidebar_position: 1 -sidebar_label: Ideal Bitcoin L2 +sidebar_label: Scaling Bitcoin --- # The Ideal Bitcoin L2 diff --git a/docs/docs/learn/introduction/overview.md b/docs/docs/learn/introduction/overview.md deleted file mode 100644 index 30327b9e..00000000 --- a/docs/docs/learn/introduction/overview.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Building on Bitcoin ---- - -# Building on Bitcoin - -BOB positions itself as the builder stack for experimentation, real-world impact, and freedom of choice. - -1. **Rust smart contracts**: Provide developers with the ability to use existing SDKs and libraries based on Rust. Extending Bitcoin with new apps (e.g., https://github.com/ordinals/ord and https://github.com/rust-nostr/nostr), libraries (e.g., https://github.com/rust-bitcoin), and SDKs (e.g., https://github.com/lightningdevkit and https://bitcoindevkit.org/) are primarily based on Rust. Existing L2 or sidechains fail to deliver on that as they are purely EVM-based or use other execution layers like Stacks and Liquid. BOB will provide builders with the ability to write smart contracts in Rust and leverage the existing ecosystem. -2. **EVM compatibility**: The EVM is not the greatest VM created. However, it benefits from a large ecosystem that is focused on improving UX via smart contract wallets, innovations in rollups, DeFi, and a maturing set of tooling. Instead of builders having to reinvent the wheel, BOB comes with the EVM built in to provide projects with access to EVM space innovations and not require them to rewrite code in a new programming language. As an added benefit, this eliminates vendor lock-in since projects can deploy on other EVM chains or spin up a BOB-compatible rollup. -3. **Rollup**: Deploying an app-specific rollup will become as simple as deploying a smart contract. With the maturing of standardized rollup stacks, the enhancements made to the rollup itself will be the key differentiators. BOB provides a Bitcoin augmentation layer enabling access to Bitcoin types and data (e.g., BRC20s, ordinals, …) and a BTC bridge to enable access to BTC. This allows builders to focus on the unique value of their application and its impact on users without having to worry about the platform risk of BOB. -4. **Bitcoin access**: BOB will support a range of BTC bridges, both institutional and decentralized, enabling builders pick the model that best suits their needs. BOB further provides trustless access to Bitcoin block and transactional data via a BTC light client (BTC Relay), as well as a range of specialized tools including but not limited to cross-chain P2P swap logic and support for advanced Bitcoin scripts such as DLCs (Discrete Log Contracts). - -![values](values.png) - -## BOB: A Bitcoin L2 for Builders - -BOB is three things: - -- A builder platform that allows anyone to create novel applications: - - BOB supports Rust smart contracts. Many Bitcoin innovations (ord, LN, nostr, BDK) happen in Rust, a mature and well-designed language. This allows innovation for new use cases without having to rewrite logic. - - BOB is EVM-compatible. Novel applications and mature tooling already exist on EVM chains. Innovators can build on top of these applications without having to rewrite existing logic in other programming languages. -- A novel BTC bridge that allows users and builders access to BTC and Bitcoin data (BRC20s, ordinals, …). -- A rollup that allows users and builders access to ETH, ERC20s, and Ethereum data (NFTs, ENS, …). - -BOB will be the catalyst for the building on Bitcoin renaissance. The movement combines the Bitcoin core values with new avenues of thought. BOB is a Bitcoin-augmented rollup for free experimentation and innovation with real-world impact. - -![BOB Components](bob-components.png) diff --git a/docs/docs/learn/introduction/stack.md b/docs/docs/learn/introduction/stack.md new file mode 100644 index 00000000..2d2ceba1 --- /dev/null +++ b/docs/docs/learn/introduction/stack.md @@ -0,0 +1,29 @@ +--- +sidebar_position: 2 +--- + +# BOB Stack + +BOB is the builder stack for experimentation, real-world impact, and freedom of choice. + +![BOB Stack](bob-stack.png) + +## Rollup Layer + +As the base layer, BOB can use any EVM rollup or chain. Initially, BOB launches on the [OP stack](https://docs.optimism.io/) including the upcoming ZK improvements by [RiscZero](https://www.risczero.com/) and can be rolled up to [Ethereum](https://www.risczero.com/) for 1-click onboarding of users, assets and liquidity. In addition, BOB can inherit Bitcoin security via a novel [merged mining](https://academy.binance.com/en/glossary/merged-mining) protocol (re-staking for Bitcoin). + +## EVM Core + +At the core, BOB leverages the [Ethereum Virtual Machine (EVM)](https://ethereum.org/en/developers/docs/evm/) to enable the creation and execution of smart contracts, primarily developed using the [Solidity smart contract programming language](https://soliditylang.org/). The EVM version deployed on BOB will be equivalent to that of Ethereum, ensuring compatibility with existing developer tooling ([Hardhat](https://hardhat.org/), [Foundry](https://getfoundry.sh/), [Remix](https://remix.ethereum.org/), …), wallets ([Metamask](https://metamask.io/), [WalletConnect](https://walletconnect.com/) supported wallets,...), best-in-class multisig ([Safe](https://www.safe.io/)), as well as key infrastructure, including block explorers like [BlockScout](https://www.blockscout.com/) and data analytics ([TheGraph](https://thegraph.com/), [Dune](https://dune.com/), [GoldSky](https://goldsky.com/)). + +## Bitcoin Bridges + +BOB provides trustless access to Bitcoin block and transactional data via a [BTC light client](https://blog.threshold.network/blockchain-relays-101/), allowing EVM contracts to process BTC transactions (e.g. P2P BTC swaps, Ordinal auctions, hashrate tokenization,...). BOB also supports a range of Bitcoin bridges, both decentralized and institutional. Through a native ETH L1/L2 bridge, BOB has access to market leader [wBTC](https://wbtc.network/) and the more secure, threshold-based version, [tBTC v2](https://threshold.network/). In the future, BOB will support advanced bridge models including opt-in collateralization (see [iBTC](https://www.interlay.io/)) and ideally a 2-way light-client BTC bridge powered by [BitVM](https://bitvm.org/bitvm.pdf). + +## Rust zkVM + +The majority of Bitcoin’s stack and applications built around it are implemented in Rust, including core [SDKs](https://github.com/rust-bitcoin/rust-bitcoin), [Lightning](https://github.com/lightningdevkit/rust-lightning), and [Ordinals](https://github.com/ordinals/ord). BOB can support Bitcoin’s Rust libraries, most notably via the [RISC Zero zkVM](https://dev.risczero.com/api/zkvm/) that allows off-chain execution of Rust programs while using [ZK proofs](https://ethereum.org/en/zero-knowledge-proofs/) to verify correct execution in EVM smart contracts. In the future, we see this as an avenue for [ZK rollups](https://vitalik.ca/general/2021/01/05/rollup.html) directly on Bitcoin where BOB itself can be proven in the zkVM and verified by Bitcoin consensus. + +## BOB SDK + +Similar to [OpenZeppelin](https://www.openzeppelin.com/) and other great Solidity libraries, BOB provides a powerful SDK for all things building on Bitcoin. This includes a wide range of Solidity contracts that can be used to interact with Bitcoin including core, [Ordinals](https://docs.ordinals.com/), [BRC20s](https://brc20.gitbook.io/brc20/overview/introduction), and [Lightning](https://lightning.network/), as well as improved inscription APIs and tools for a unified BTC and EVM wallet experience (e.g. manage Ordinals in your MetaMask wallet via [Snaps](https://metamask.io/snaps/)). Plus the ability to leverage [Account Abstraction](https://ethereum.org/en/roadmap/account-abstraction/) with bridged BTC. diff --git a/docs/docs/learn/introduction/use-cases.md b/docs/docs/learn/introduction/use-cases.md index 1fa6e6a1..9cf237d7 100644 --- a/docs/docs/learn/introduction/use-cases.md +++ b/docs/docs/learn/introduction/use-cases.md @@ -1,6 +1,6 @@ --- sidebar_position: 3 -sidebar_label: Build with BOB +sidebar_label: Use Case Ideas --- # Use Case Ideas diff --git a/docs/docs/learn/introduction/values.excalidraw b/docs/docs/learn/introduction/values.excalidraw deleted file mode 100644 index 1164e8a4..00000000 --- a/docs/docs/learn/introduction/values.excalidraw +++ /dev/null @@ -1,391 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "id": "WpQRmBQrZbLK35c5dKsdG", - "type": "rectangle", - "x": 499.83203125, - "y": 245.96484375, - "width": 309.765625, - "height": 147.8515625000001, - "angle": 0, - "strokeColor": "#f08c00", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "seed": 1838345696, - "version": 115, - "versionNonce": 1820718560, - "isDeleted": false, - "boundElements": null, - "updated": 1693286586207, - "link": null, - "locked": false - }, - { - "id": "ShGCZa6eRQQDpQ17I0Ooi", - "type": "text", - "x": 530, - "y": 215.796875, - "width": 91.08393859863281, - "height": 35, - "angle": 0, - "strokeColor": "#f08c00", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 887060960, - "version": 32, - "versionNonce": 2116772320, - "isDeleted": false, - "boundElements": null, - "updated": 1693286711148, - "link": null, - "locked": false, - "text": "Bitcoin", - "fontSize": 28, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 25, - "containerId": null, - "originalText": "Bitcoin", - "lineHeight": 1.25 - }, - { - "id": "fbbCNG_bYyIsmLkZf1J62", - "type": "text", - "x": 510.796875, - "y": 269.390625, - "width": 178.83984375, - "height": 100, - "angle": 0, - "strokeColor": "#f08c00", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 765718560, - "version": 265, - "versionNonce": 2027867168, - "isDeleted": false, - "boundElements": null, - "updated": 1693286581301, - "link": null, - "locked": false, - "text": "- Scarcity\n- Immutability\n- Decentralization\n- Portability", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 93, - "containerId": null, - "originalText": "- Scarcity\n- Immutability\n- Decentralization\n- Portability", - "lineHeight": 1.25 - }, - { - "type": "rectangle", - "version": 71, - "versionNonce": 784447968, - "isDeleted": false, - "id": "byWWUb866C6Og_IfTY59m", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 982.828125, - "y": 238.30859375, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 309.765625, - "height": 158.8203125, - "seed": 1145905184, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [], - "updated": 1693286092181, - "link": null, - "locked": false - }, - { - "id": "qhDuHSDy_W_O8I7Vdai7u", - "type": "text", - "x": 1215.66015625, - "y": 201.4609375, - "width": 61.03996276855469, - "height": 35, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 351784992, - "version": 142, - "versionNonce": 1566306784, - "isDeleted": false, - "boundElements": null, - "updated": 1693286717573, - "link": null, - "locked": false, - "text": "BOB", - "fontSize": 28, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 25, - "containerId": null, - "originalText": "BOB", - "lineHeight": 1.25 - }, - { - "id": "wCO8uCaGR0vgdcsom2j7r", - "type": "text", - "x": 993, - "y": 269, - "width": 198.87985229492188, - "height": 75, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 1352938976, - "version": 205, - "versionNonce": 534986208, - "isDeleted": false, - "boundElements": [ - { - "id": "Keao_NUs7_f4_ugDAkAb0", - "type": "arrow" - } - ], - "updated": 1693286623549, - "link": null, - "locked": false, - "text": "- Experimentation\n- Real-World Impact\n- Freedom of Choice", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 68, - "containerId": null, - "originalText": "- Experimentation\n- Real-World Impact\n- Freedom of Choice", - "lineHeight": 1.25 - }, - { - "id": "Keao_NUs7_f4_ugDAkAb0", - "type": "arrow", - "x": 978.26171875, - "y": 258.90625, - "width": 173.87890625, - "height": 38.23046875, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "seed": 791446560, - "version": 151, - "versionNonce": 2119008288, - "isDeleted": false, - "boundElements": null, - "updated": 1693286655200, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -85.421875, - -38.23046875 - ], - [ - -173.87890625, - -0.33984375 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "wCO8uCaGR0vgdcsom2j7r", - "focus": -0.04276101897334255, - "gap": 14.73828125 - }, - "endBinding": null, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "FHScA5Hr7ngKb10aMu2eh", - "type": "arrow", - "x": 803.72265625, - "y": 382.44921875, - "width": 182.39453125, - "height": 35.0625, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "seed": 131397664, - "version": 122, - "versionNonce": 822281248, - "isDeleted": false, - "boundElements": null, - "updated": 1693286653161, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 92.265625, - 35.0625 - ], - [ - 182.39453125, - 1.07421875 - ] - ], - "lastCommittedPoint": null, - "startBinding": null, - "endBinding": null, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "TgfHBgZq8Z-yb1Xine1AN", - "type": "text", - "x": 739.078125, - "y": 437.39453125, - "width": 364.1796875, - "height": 25, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 1225053664, - "version": 142, - "versionNonce": 474159136, - "isDeleted": false, - "boundElements": null, - "updated": 1693286746634, - "link": null, - "locked": false, - "text": "Fallback to Bitcoin to protect users", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 18, - "containerId": null, - "originalText": "Fallback to Bitcoin to protect users", - "lineHeight": 1.25 - }, - { - "type": "text", - "version": 199, - "versionNonce": 1146777632, - "isDeleted": false, - "id": "7zmq9FbF-gl6MozbjRUOm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 738.6246109008789, - "y": 181.3359375, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 373.17962646484375, - "height": 25, - "seed": 2036558304, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1693286723811, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Provide value through experimentation", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Provide value through experimentation", - "lineHeight": 1.25, - "baseline": 18 - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - }, - "files": {} -} \ No newline at end of file diff --git a/docs/docs/learn/introduction/values.png b/docs/docs/learn/introduction/values.png deleted file mode 100644 index de2a093c..00000000 Binary files a/docs/docs/learn/introduction/values.png and /dev/null differ diff --git a/docs/docs/learn/networks/_category_.yml b/docs/docs/learn/networks/_category_.yml new file mode 100644 index 00000000..c3cf371f --- /dev/null +++ b/docs/docs/learn/networks/_category_.yml @@ -0,0 +1,4 @@ +position: 3 +label: 'Networks' +collapsible: true +collapsed: true \ No newline at end of file diff --git a/docs/docs/learn/networks/op-stack.md b/docs/docs/learn/networks/op-stack.md new file mode 100644 index 00000000..6602ee9a --- /dev/null +++ b/docs/docs/learn/networks/op-stack.md @@ -0,0 +1,58 @@ +--- +sidebar_position: 1 +--- +# BOB on the OP Stack + +The first deployment of BOB will be using the [OP Stack](https://stack.optimism.io), benefiting from infrastructure, marketing, and BD support. Eventually, The BOB OP-Stack deployment can be [Superchain-compatible](https://www.optimism.io/superchain) making it easy for users to move seamlessly between different Superchain rollups. + +We are working on extending the functionality of the OP stack specifically for Bitcoin: (1) fees payable in tokenized BTC via [OpenGSN](https://opengsn.org/) and [account abstraction](https://ethereum.org/en/roadmap/account-abstraction/) to ensure easy on-ramp from Bitcoin, and (2) in the mid-term, support for Bitcoin as [data availability](https://ethereum.org/en/developers/docs/data-availability/) layer. Where possible, we implement smart contracts directly on the EVM to ensure OP stack updates can be directly applied to BOB. + +## Why OP Stack? + +There are several great options for rollups including building them from scratch or using zk rollups ([Starkware](https://starkware.co/), [zkSync](https://zksync.io/), [Polygon](https://polygon.technology/polygon-zkevm), [Scroll](https://scroll.io/), ...) and optimistic rollups ([Arbitrum](https://arbitrum.io/), [OP Stack](https://docs.optimism.io/stack/getting-started), ...). BOB's key values are experimentation, real-world impact, and freedom of choice. + +In our opinion, OP Stack aligns best with these values as it allows developers to quickly deploy new protocols without having to reimplement existing protocols in new programming languages. It also gives users choice as the Superchain concept allows users to freely move between OP Stack rollups. We have further considered Arbitrum, but opted against this since, under the hood, Arbitrum uses WASM and has more incompatibilities than OP Stack (albeit the team is working very hard to mitigate those). ZK rollups are too untested for our preference and require smart contract modifications (type 3 and 4 zkEVM rollups), are slow (type 1 and 2 zkEVM), or require complete rewrites of wallet software and integrations (Starkware, ...). + +The way that BOB is built, it should be able to be deployed on any EVM rollup stack without major changes. + +## Adopting the OP Stack + +BOB is adopting several changes compared to the standard OP Stack: + +### Governance + +BOB will have its own governance to reach community decisions and enact changes. + +If the BOB Rollup chooses to be comptabile with the Superchain, then parts of BOB will be subject to OP governance with the Superchain upgrade. An OP Security Council will be capable of enacting security upgrades of the OP chain and L1 and L2 bridge contracts. + +### Data Availability + +A Data Availability (DA) layer stores the raw inputs to the state transition function for the execution layer. The OP Stack supports [multiple DAs but sets Ethereum as the de facto DA](https://stack.optimism.io/docs/understand/landscape/#data-availability). The OP Stack Superchain is considering a dedicated [Plasma DA chain](https://stack.optimism.io/docs/understand/explainer/#alt-data-availability-layer-plasma-protocol) to address the rising cost and limited scalability of Ethereum as a DA layer. + +BOB is using Ethereum on testnet, but we are investigating alternatives for mainnet launch: + +- Bitcoin: The Celestia team has a specification for [using Bitcoin as a DA layer](https://github.com/rollkit/bitcoin-da/blob/main/spec.md) that suits BOB needs and would ensure compatibility with Celestia deployments. Using Bitcoin as a DA layer would introduce complexity around wallets as users would need to provide BTC alongside their EVM transactions to pay for DA costs. +- Celestia: If Celestia were to add an option to pay for DA cost in (bridged) Bitcoin, Celestia might become a cheaper and decent alternative to using Ethereum or Bitcoin as a DA layer. + +### Sequencing + +The OP Stack uses a single, centralized sequencer. BOB will use the same sequencer model for its launch but with the addition of merged mining, add functionality to verify the work of the sequencer by Bitcoin miners. + +We are welcoming efforts to decentralize sequencers pushed by the OP Stack. + +### Execution + +OP Stack uses a fork of go-ethereum called op-geth. BOB has no changes to op-geth for the testnet deployment. + +Instead of changing the execution client, necessary features including proving Bitcoin state and paying transaction fees in bridged Bitcoin are added within the EVM as smart contracts. This has the advantage that: + +1. The BOB OP Stack deployment stays up-to-date with improvements made by other teams improving the OP Stack without having to maintain a custom fork. In turn, this ensures that features available on other EVM chains and rollups are 1:1 applicable to BOB. +2. Technology built on top of the EVM within BOB like the merged mining security, paying in bridged Bitcoin for transaction fees, and the BTC light client can be deployed on any EVM chain. BOB is thus ready for a multi-chain and multi-rollup world. + +### Settlement + +The OP Stack settles on Ethereum. Currently, the OP Stack is lacking fraud proofs which is a major downside of using OP Stack as users cannot prove fraudulent behavior by the sequencer. + +However, OP Stack has a candidate for fraud proofs in testing and is actively working on increasing the efficiency of fraud proofs using ZK. + +We are working towards an approach to add merged mining security to the rollup by making a PoW part of state transition. Consequently, a lack of PoW constitutes a fault by the sequencer. Thereby, Bitcoin miners validate the rollup to offset the trust in the sequencer (in both the centralized and decentralized sequencer case). \ No newline at end of file diff --git a/docs/docs/learn/technology/_category_.yml b/docs/docs/learn/technology/_category_.yml new file mode 100644 index 00000000..34416493 --- /dev/null +++ b/docs/docs/learn/technology/_category_.yml @@ -0,0 +1,4 @@ +position: 4 +label: 'Technology' +collapsible: true +collapsed: true \ No newline at end of file diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index a573f24f..4e14c846 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -9,6 +9,8 @@ const LANDING_PAGE = "https://gobob.xyz"; const DOCS_PAGE = "https://docs.gobob.xyz"; const DISCORD = "https://discordapp.com/invite/interlay"; const TWITTER = "https://twitter.com/build_on_bob"; +const TELEGRAM = "https://t.me/+CyIcLW2nfaFlNDc1"; +const FORUM = "https://forum.gobob.xyz"; /** @type {import('@docusaurus/types').Config} */ const config = { @@ -130,17 +132,34 @@ const config = { label: "Twitter", href: TWITTER, }, + { + label: "Forum", + href: FORUM, + }, ], }, { - title: "More", + title: "Builders", items: [ + { + label: "Telegram (Dev)", + href: TELEGRAM, + }, { label: "GitHub", href: GITHUB_LINK, }, ], }, + { + title: "More", + items: [ + { + label: "BOB Homepage", + href: LANDING_PAGE, + }, + ], + }, ], copyright: `Built with ❤️ by the BOB Collective. ${new Date().getFullYear()}.`, }, diff --git a/docs/src/pages/index.mdx b/docs/src/pages/index.mdx index cd61e805..2ea7dcc3 100644 --- a/docs/src/pages/index.mdx +++ b/docs/src/pages/index.mdx @@ -8,18 +8,22 @@ import styles from "./index.module.css";

Build on Bitcoin

- A layer 2 stack empowering everyone to build and innovate on Bitcoin. + BOB is a layer 2 stack empowering everyone to build and innovate on Bitcoin.

-## Builder Values +## What is BOB? -BOB encompasses a set of core values enabling builders to innovate on Bitcoin. +BOB combines the power of Bitcoin security and stability with innovation enabled by the EVM and the OP stack. BOB supports the Bitcoin ecosystem incl. Ordinals, Lightning and Nostr, powered by cross-chain light clients, a universal Bitcoin smart contract SDK and the RiscZero zkVM. -- **Experimentation**: Technology (rollups, ZK, AI, …), art (NFTs, …), organization (DAOs), and entertainment (social, …). We don’t close off BOB to areas but instead want a diverse space of innovators. -- **Real-world impact**: The innovations made should have an improvement for people by giving them access to Bitcoin values. BOB is an enabler to bring the BTC advantages to new and existing use cases. -- **Freedom of choice**: Projects deployed on BOB should also be able to deploy on other rollups and chains. Builders should focus on the unique values of the product without having to worry about platform risk of BOB. +Our mission is to create a Bitcoin rollup ecosystem to power innovation and experimentation without relying on Bitcoin forks. + +## Values + +- **Experimentation**: We enable a diverse space of innovators to play with ordinals, ZK, SocialFi, DAOs, and more. +- **Real-world impact**: Innovations improve people's lives by giving them access to Bitcoin values. +- **Freedom of choice**: Builders focus on the unique values of their projects without having to re-invent the wheel.
diff --git a/foundry.toml b/foundry.toml index 575f1994..19188228 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,7 @@ src = "src" out = "out" libs = ["lib"] +solc = "0.8.17" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/account-abstraction b/lib/account-abstraction new file mode 160000 index 00000000..abff2aca --- /dev/null +++ b/lib/account-abstraction @@ -0,0 +1 @@ +Subproject commit abff2aca61a8f0934e533d0d352978055fddbd96 diff --git a/lib/gsn b/lib/gsn new file mode 160000 index 00000000..da4222b7 --- /dev/null +++ b/lib/gsn @@ -0,0 +1 @@ +Subproject commit da4222b76e3ae1968608dc5c5d80074dcac7c4be diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 00000000..5fe3d82a --- /dev/null +++ b/remappings.txt @@ -0,0 +1,10 @@ +@bob-collective/bitcoin-spv/=lib/bitcoin-spv/src/ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +@openzeppelin/=lib/openzeppelin-contracts/ +openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/ +account-abstraction/=lib/account-abstraction/contracts/ +bitcoin-spv/=lib/bitcoin-spv/ +gsn.git/=lib/gsn/ +@opengsn/=lib/gsn/ +@account-abstraction=lib/account-abstraction/ \ No newline at end of file diff --git a/script/AATokenPaymaster.sol b/script/AATokenPaymaster.sol new file mode 100644 index 00000000..82ee3541 --- /dev/null +++ b/script/AATokenPaymaster.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; +import {PimlicoERC20Paymaster, IERC20, IEntryPoint} from "../src/paymasters/AccountAbstraction/AATokenPaymaster.sol"; +import {DummyOracle} from "../src/paymasters/Oracle.sol"; + +contract AATokenPaymasterScript is Script { + function setUp() public {} + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + IERC20 token = IERC20(vm.envAddress("ERC_ADDRESS")); + int256 tokenPrice = vm.envInt("ERC_PRICE"); + uint8 tokenDecimals = uint8(vm.envUint("ERC_DECIMALS")); + IEntryPoint entrypoint = IEntryPoint(vm.envAddress("ENTRYPOINT_ADDRESS")); + address owner = vm.envAddress("OWNER_ADDRESS"); + + vm.startBroadcast(deployerPrivateKey); + + DummyOracle ethOracle = new DummyOracle(); + ethOracle.setPrice(189100000000); // 1 eth = 1891usd + DummyOracle tokenOracle = new DummyOracle(); + tokenOracle.setPrice(tokenPrice); // set usd price * 10^8 + + PimlicoERC20Paymaster paymaster = + new PimlicoERC20Paymaster(token, entrypoint, tokenOracle, ethOracle, owner, tokenDecimals); + + paymaster.updatePrice(); + paymaster.addStake{value: 0.1 ether}(1); + entrypoint.depositTo{value: 1 ether}(address(paymaster)); + vm.stopBroadcast(); + } +} diff --git a/script/Gsn.sol b/script/Gsn.sol new file mode 100644 index 00000000..35b6b083 --- /dev/null +++ b/script/Gsn.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; +import "../src/paymasters/OracleTokenPaymaster.sol"; +import {DummyOracle} from "../src/paymasters/Oracle.sol"; +import {IRelayHub} from "../lib/gsn/packages/contracts/src/interfaces/IRelayHub.sol"; +import {TestingErc20} from "../src/TestingErc20.sol"; + +contract TestingWbtcScript is Script { + function setUp() public {} + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + address forwarder = vm.envAddress("FORWARDER_ADDRESS"); + int256 tokenPrice = vm.envInt("ERC_PRICE"); + // address payable mtPaymaster = payable(vm.envAddress("MT_PAYMASTER_ADDRESS")); + + vm.startBroadcast(deployerPrivateKey); + + TestingErc20 token = new TestingErc20("Wrapped BTC", "WBTC", 8); + token.setTrustedForwarder(forwarder); + + DummyOracle oracle = new DummyOracle(); + oracle.setPrice(tokenPrice); + + // OracleTokenPaymaster paymaster = OracleTokenPaymaster(mtPaymaster); + // mtPaymaster.addOracle(token, token.decimals(), oracle); + + vm.stopBroadcast(); + } +} diff --git a/script/Marketplace.sol b/script/Marketplace.sol new file mode 100644 index 00000000..6cc8ca2d --- /dev/null +++ b/script/Marketplace.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; +import {BtcMarketPlace} from "../src/swap/Btc_Marketplace.sol"; +import {MarketPlace} from "../src/swap/Marketplace.sol"; +import {TestLightRelay} from "../src/relay/TestLightRelay.sol"; + +contract MarketplaceScript is Script { + function setUp() public {} + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address forwarder = vm.envAddress("FORWARDER_ADDRESS"); + + vm.startBroadcast(deployerPrivateKey); + BtcMarketPlace btcMarketplace = new BtcMarketPlace(new TestLightRelay(), forwarder); + MarketPlace marketplace = new MarketPlace(forwarder); + + vm.stopBroadcast(); + } +} diff --git a/script/OnboardingPaymaster.sol b/script/OnboardingPaymaster.sol new file mode 100644 index 00000000..871c9d87 --- /dev/null +++ b/script/OnboardingPaymaster.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; +import "../src/paymasters/OnboardingPaymaster.sol"; +import {DummyOracle} from "../src/paymasters/Oracle.sol"; +// import {IERC2771} from "../lib/gsn/packages/contracts/src/interfaces/IERC2771Recipient.sol"; +import {IRelayHub} from "../lib/gsn/packages/contracts/src/interfaces/IRelayHub.sol"; + +contract OnboardingPaymasterScript is Script { + function setUp() public {} + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + IRelayHub relay = IRelayHub(vm.envAddress("RELAY_ADDRESS")); + // bytes memory b = vm.envBytes("WHITELIST_SELECTOR"); + // (uint32 whitelistSelector) = abi.decode(b, (uint32)); + uint256 tmp = vm.envUint("WHITELIST_SELECTOR"); + address forwarder = vm.envAddress("FORWARDER_ADDRESS"); + uint32 whitelistSelector = uint32(tmp); + address whitelistAddress = vm.envAddress("WHITELIST_ADDRESS"); + + vm.startBroadcast(deployerPrivateKey); + OnboardingPaymaster paymaster = new OnboardingPaymaster(whitelistAddress, whitelistSelector); + + relay.depositFor{value: 1 ether}(address(paymaster)); + paymaster.setRelayHub(relay); + paymaster.setTrustedForwarder(forwarder); + + vm.stopBroadcast(); + } +} diff --git a/script/OracleTokenPaymaster.sol b/script/OracleTokenPaymaster.sol new file mode 100644 index 00000000..cbe00fda --- /dev/null +++ b/script/OracleTokenPaymaster.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; +import "../src/paymasters/OracleTokenPaymaster.sol"; +import {DummyOracle} from "../src/paymasters/Oracle.sol"; +// import {IERC2771} from "../lib/gsn/packages/contracts/src/interfaces/IERC2771Recipient.sol"; +import {IRelayHub} from "../lib/gsn/packages/contracts/src/interfaces/IRelayHub.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract OracleTokenPaymasterScript is Script { + function setUp() public {} + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + IRelayHub relay = IRelayHub(vm.envAddress("RELAY_ADDRESS")); + address forwarder = vm.envAddress("FORWARDER_ADDRESS"); + ERC20 token = ERC20(vm.envAddress("ERC_ADDRESS")); + IOracle oracle = IOracle(vm.envAddress("ORACLE_ADDRESS")); + // IERC2771 target = IERC2771(vm.envAddress("TARGET_ADDRESS")); + + vm.startBroadcast(deployerPrivateKey); + DummyOracle nativeTokenOracle = new DummyOracle(); + OracleTokenPaymaster paymaster = new OracleTokenPaymaster(nativeTokenOracle); + + relay.depositFor{value: 1 ether}(address(paymaster)); + paymaster.setRelayHub(relay); + paymaster.setTrustedForwarder(forwarder); + + nativeTokenOracle.setPrice(189100000000); // 1 eth = 1891usd + + paymaster.addOracle(token, token.decimals(), oracle); + + vm.stopBroadcast(); + } +} diff --git a/script/TestingWbtc.sol b/script/TestingWbtc.sol new file mode 100644 index 00000000..84c78e06 --- /dev/null +++ b/script/TestingWbtc.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; +import "../src/paymasters/OracleTokenPaymaster.sol"; +import {DummyOracle} from "../src/paymasters/Oracle.sol"; +// import {IERC2771} from "../lib/gsn/packages/contracts/src/interfaces/IERC2771Recipient.sol"; +import {IRelayHub} from "../lib/gsn/packages/contracts/src/interfaces/IRelayHub.sol"; +import {TestingErc20} from "../src/TestingErc20.sol"; + +contract TestingWbtcScript is Script { + function setUp() public {} + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address forwarder = vm.envAddress("FORWARDER_ADDRESS"); + int256 tokenPrice = vm.envInt("ERC_PRICE"); + // address payable mtPaymaster = payable(vm.envAddress("MT_PAYMASTER_ADDRESS")); + + vm.startBroadcast(deployerPrivateKey); + + TestingErc20 token = new TestingErc20("Wrapped BTC", "WBTC", 8); + token.setTrustedForwarder(forwarder); + + DummyOracle oracle = new DummyOracle(); + oracle.setPrice(tokenPrice); + + new TestingErc20("USD Coin", "USDC", 6); + new TestingErc20("Tether USD", "USDT", 6); + + vm.stopBroadcast(); + } +} diff --git a/sdk/package-lock.json b/sdk/package-lock.json new file mode 100644 index 00000000..56a48a2b --- /dev/null +++ b/sdk/package-lock.json @@ -0,0 +1,1854 @@ +{ + "name": "@gobob/bob-sdk", + "version": "1.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@gobob/bob-sdk", + "version": "1.1.1", + "dependencies": { + "bitcoinjs-lib": "^6.1.5" + }, + "devDependencies": { + "@types/chai": "^4.3.6", + "@types/mocha": "^10.0.2", + "@types/node": "^20.8.9", + "chai": "^4.3.10", + "ecpair": "^2.1.0", + "mocha": "^10.2.0", + "tiny-secp256k1": "^2.2.3", + "ts-mocha": "^10.0.0", + "ts-node-dev": "^2.0.0", + "typescript": "^5.2.2", + "yargs": "^17.5.1" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "optional": true + }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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 + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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 + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", + "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "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/chalk/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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 + }, + "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 + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ecpair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "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 + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "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, + "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 + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "optional": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "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 + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "dependencies": { + "wrappy": "1" + } + }, + "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, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", + "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", + "dev": true, + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", + "integrity": "sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==", + "dev": true, + "dependencies": { + "ts-node": "7.0.1" + }, + "bin": { + "ts-mocha": "bin/ts-mocha" + }, + "engines": { + "node": ">= 6.X.X" + }, + "optionalDependencies": { + "tsconfig-paths": "^3.5.0" + }, + "peerDependencies": { + "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X" + } + }, + "node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-node-dev/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node-dev/node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "optional": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-tools": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dev": true, + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wif/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/wif/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/wif/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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 + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sdk/package.json b/sdk/package.json index 06627e14..8779e6cc 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@gobob/bob-sdk", - "version": "1.1.0", + "version": "1.1.1", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { @@ -32,4 +32,4 @@ "dependencies": { "bitcoinjs-lib": "^6.1.5" } -} \ No newline at end of file +} diff --git a/sdk/src/electrs.ts b/sdk/src/electrs.ts index d8ed849e..945ca14f 100644 --- a/sdk/src/electrs.ts +++ b/sdk/src/electrs.ts @@ -30,6 +30,8 @@ export interface UTXO { txid: string vout: number, value: number, + confirmed: boolean, + height?: number } export interface ElectrsClient { @@ -134,7 +136,7 @@ export interface ElectrsClient { /** * Get the Unspent Transaction Outputs (UTXOs) for an address. * - * @param {string} address - The Bitcoin address to checl. + * @param {string} address - The Bitcoin address to check. * @returns {Promise>} A promise that resolves to an array of UTXOs. */ getAddressUtxos(address: string): Promise>; @@ -269,7 +271,9 @@ export class DefaultElectrsClient implements ElectrsClient { return { txid: utxo.txid, vout: utxo.vout, - value: utxo.value + value: utxo.value, + confirmed: utxo.status.confirmed, + height: utxo.status.block_height } }); } diff --git a/sdk/src/helpers.ts b/sdk/src/helpers.ts new file mode 100644 index 00000000..5647427e --- /dev/null +++ b/sdk/src/helpers.ts @@ -0,0 +1,59 @@ +import { ElectrsClient, UTXO } from "./electrs"; +import { parseInscriptions } from "./inscription"; +import { InscriptionId, OrdinalsClient } from "./ordinal-api"; +import * as bitcoin from "bitcoinjs-lib"; + +// Get the (encoded) inscription IDs for the address +export async function getInscriptionIds(electrsClient: ElectrsClient, ordinalsClient: OrdinalsClient, bitcoinAddress: string) { + const utxos = await electrsClient.getAddressUtxos(bitcoinAddress); + const inscriptionIds = await Promise.all( + utxos.sort((a, b) => { + // force large number if height is not available (as expected for unconfirmed utxo) + const heightA = a.height || Number.MAX_SAFE_INTEGER; + const heightB = b.height || Number.MAX_SAFE_INTEGER; + + return heightA - heightB; + }).map(utxo => getInscriptionIdsForUtxo(electrsClient, ordinalsClient, utxo)) + ); + return inscriptionIds.flat(); +} + +// Get the (encoded) inscription IDs for the UTXO +async function getInscriptionIdsForUtxo(electrsClient: ElectrsClient, ordinalsClient: OrdinalsClient, utxo: UTXO) { + if (utxo.confirmed) { + // use ord api if the tx has been included in a block + const outputJson = await ordinalsClient.getInscriptionsFromOutPoint(utxo); + return outputJson.inscriptions; + } + + const txHex = await electrsClient.getTransactionHex(utxo.txid); + const tx = bitcoin.Transaction.fromHex(txHex); + + // FIXME: assumes inscriptions are always sent to the first output + // which is not always the case, we should compare the satpoint + if (utxo.vout == 0) { + // this handles the case where we have just transferred an inscription + // but the ordinal indexer has not yet confirmed it so we check if the + // parent utxo has an inscription instead + // NOTE: this won't work if the parent UTXO is not included in a block + const parentInscriptions = await Promise.all(tx.ins.map(async txInput => { + const txid = txInput.hash.reverse().toString("hex"); + const outputJson = await ordinalsClient.getInscriptionsFromOutPoint({ txid, vout: txInput.index }); + return outputJson.inscriptions; + })); + const inscriptionIds = parentInscriptions.flat(); + if (inscriptionIds.length > 0) { + return inscriptionIds; + } + } + + // otherwise parse the inscriptions manually + const inscriptions = parseInscriptions(tx); + // FIXME: also assumes inscription is made on the first sat of the first output + if (utxo.vout != 0) { + return []; + } else { + // NOTE: it is possible for the inscription to be invalid if the sat is already inscribed + return inscriptions.map((_, index) => InscriptionId.toString({ txid: utxo.txid, index })); + } +} \ No newline at end of file diff --git a/sdk/src/index.ts b/sdk/src/index.ts index b92045a2..80edeaf9 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -2,3 +2,4 @@ export * from "./electrs"; export * from "./relay"; export * from "./utils"; export * from "./ordinals"; +export * from "./helpers"; diff --git a/sdk/src/ordinals/commit.ts b/sdk/src/ordinals/commit.ts index 0aa83bd6..8238eed8 100644 --- a/sdk/src/ordinals/commit.ts +++ b/sdk/src/ordinals/commit.ts @@ -6,8 +6,7 @@ const encoder = new TextEncoder(); export const MAX_CHUNK_SIZE = 520; export interface Inscription { - // MIME type - contentType: Buffer; + contentType: Buffer; // MIME type content: Buffer; } diff --git a/sdk/src/ordinals/index.ts b/sdk/src/ordinals/index.ts index 637b38a8..380f759d 100644 --- a/sdk/src/ordinals/index.ts +++ b/sdk/src/ordinals/index.ts @@ -1,10 +1,10 @@ import * as bitcoin from "bitcoinjs-lib"; import { DummySigner, RemoteSigner } from "./signer"; -import { CommitTxData, createCommitTxData, createTextInscription } from "./commit"; +import { CommitTxData, Inscription, createCommitTxData, createInscription, createTextInscription } from "./commit"; import { createRevealTx, customFinalizer, signRevealTx } from "./reveal"; -export { RemoteSigner }; +export { RemoteSigner, createInscription, createTextInscription, Inscription }; /** * Estimate the virtual size of a 1 input 1 output reveal tx. @@ -42,26 +42,25 @@ function estimateTxSize( } /** - * Inscribe some text data on Bitcoin using the remote signer. + * Inscribe some data on Bitcoin using the remote signer. * * @param signer - Implementation to interact with Bitcoin and sign the PSBT. * @param toAddress - The address to receive the inscription. * @param feeRate - Fee rate of the Bitcoin network (satoshi / byte). - * @param text - Data to inscribe in the witness of the reveal transaction. + * @param inscription - Data to inscribe in the witness of the reveal transaction. * @param postage - Amount of postage to include in the inscription. * @returns Promise which resolves to the reveal transaction. */ -export async function inscribeText( +export async function inscribeData( signer: RemoteSigner, toAddress: string, feeRate: number, - text: string, + inscription: Inscription, postage = 10000, ) { const bitcoinNetwork = await signer.getNetwork(); const publicKey = Buffer.from(await signer.getPublicKey(), "hex"); - const inscription = createTextInscription(text); const commitTxData = createCommitTxData(bitcoinNetwork, publicKey, inscription); const revealTxSize = estimateTxSize(bitcoinNetwork, publicKey, commitTxData, toAddress, postage); diff --git a/sdk/test/ordinals.test.ts b/sdk/test/ordinals.test.ts index 18449976..be041b08 100644 --- a/sdk/test/ordinals.test.ts +++ b/sdk/test/ordinals.test.ts @@ -1,10 +1,10 @@ import { assert } from "chai"; import * as ecc from "tiny-secp256k1"; import * as ECPairFactory from "ecpair"; -import { RemoteSigner, inscribeText } from "../src/ordinals"; +import { RemoteSigner, inscribeData } from "../src/ordinals"; import { Network, Psbt, Transaction, address, initEccLib } from "bitcoinjs-lib"; import { bitcoin } from "bitcoinjs-lib/src/networks"; -import { chunkContent, MAX_CHUNK_SIZE } from "../src/ordinals/commit"; +import { chunkContent, createTextInscription, MAX_CHUNK_SIZE } from "../src/ordinals/commit"; const ECPair = ECPairFactory.default(ecc); initEccLib(ecc); @@ -51,7 +51,7 @@ describe("Ordinal Tests", () => { const secret = "fc7458de3d5616e7803fdc81d688b9642641be32fee74c4558ce680cac3d4111"; const signer = new StaticSigner(secret); const toAddress = "bc1pxaneaf3w4d27hl2y93fuft2xk6m4u3wc4rafevc6slgd7f5tq2dqyfgy06"; - const tx = await inscribeText(signer, toAddress, 1, "Hello World!", 546); + const tx = await inscribeData(signer, toAddress, 1, createTextInscription("Hello World!"), 546); assert(tx.getId() == "9312bc8a9541dd3e4b22993740ff96449a52dbca00b8be22b2979bb25053f7d6"); }); diff --git a/src/TestingErc20.sol b/src/TestingErc20.sol new file mode 100644 index 00000000..4e4cae59 --- /dev/null +++ b/src/TestingErc20.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {ERC2771Recipient} from "@opengsn/packages/contracts/src/ERC2771Recipient.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// created using https://wizard.openzeppelin.com/ and then modified. +// todo: naming +contract TestingErc20 is ERC20, ERC20Burnable, Ownable, ERC2771Recipient { + uint8 _numDecimals; + + constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol) { + _numDecimals = _decimals; + } + + function setTrustedForwarder(address _forwarder) public onlyOwner { + _setTrustedForwarder(_forwarder); + } + + function mint(uint256 amount) external { + _mint(_msgSender(), amount); + } + + function _msgSender() internal view override(Context, ERC2771Recipient) returns (address sender) { + sender = ERC2771Recipient._msgSender(); + } + + function _msgData() internal view override(Context, ERC2771Recipient) returns (bytes calldata) { + return ERC2771Recipient._msgData(); + } + + // override decimals + function decimals() public view virtual override returns (uint8) { + return _numDecimals; + } +} diff --git a/src/bridge/BitcoinTx.sol b/src/bridge/BitcoinTx.sol index 12011a7c..7f6850b9 100644 --- a/src/bridge/BitcoinTx.sol +++ b/src/bridge/BitcoinTx.sol @@ -126,6 +126,17 @@ library BitcoinTx { bytes bitcoinHeaders; } + /// @notice Represents info about an unspent transaction output. + struct UTXO { + /// @notice Hash of the transaction the output belongs to. + /// @dev Byte order corresponds to the Bitcoin internal byte order. + bytes32 txHash; + /// @notice Index of the transaction output (0-indexed). + uint32 txOutputIndex; + /// @notice Value of the transaction output. + uint64 txOutputValue; + } + /// @notice Validates the SPV proof of the Bitcoin transaction. /// Reverts in case the validation or proof verification fail. /// @param txInfo Bitcoin transaction data. @@ -278,4 +289,43 @@ library BitcoinTx { revert("No output found for scriptPubKey"); } + + function reverseEndianness(bytes32 b) internal pure returns (bytes32 txHash) { + bytes memory newValue = new bytes(b.length); + for (uint256 i = 0; i < b.length; i++) { + newValue[b.length - i - 1] = b[i]; + } + assembly { + txHash := mload(add(newValue, 32)) + } + } + + function ensureTxInputSpendsUtxo(bytes memory _vin, BitcoinTx.UTXO memory utxo) internal pure { + uint256 _varIntDataLen; + uint256 _nIns; + + (_varIntDataLen, _nIns) = BTCUtils.parseVarInt(_vin); + require(_varIntDataLen != BTCUtils.ERR_BAD_ARG, "Read overrun during VarInt parsing"); + + uint256 _len = 0; + uint256 _offset = 1 + _varIntDataLen; + + bytes32 expectedTxHash = reverseEndianness(utxo.txHash); + + for (uint256 _i = 0; _i < _nIns; _i++) { + bytes32 outpointTxHash = _vin.extractInputTxIdLeAt(_offset); + uint32 outpointIndex = BTCUtils.reverseUint32(uint32(_vin.extractTxIndexLeAt(_offset))); + + // check if it matches tx + if (expectedTxHash == outpointTxHash && utxo.txOutputIndex == outpointIndex) { + return; + } + + _len = BTCUtils.determineInputLengthAt(_vin, _offset); + require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in scriptSig"); + _offset = _offset + _len; + } + + revert("Transaction does not spend the required utxo"); + } } diff --git a/src/faucet/Erc20Minter.sol b/src/faucet/Erc20Minter.sol index 40566617..a6fe3175 100644 --- a/src/faucet/Erc20Minter.sol +++ b/src/faucet/Erc20Minter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.18; +pragma solidity ^0.8.13; import "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/src/paymasters/AccountAbstraction/AATokenPaymaster.sol b/src/paymasters/AccountAbstraction/AATokenPaymaster.sol new file mode 100644 index 00000000..75f2db2c --- /dev/null +++ b/src/paymasters/AccountAbstraction/AATokenPaymaster.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +// adapted from https://github.com/pimlicolabs/erc20-paymaster-contracts/blob/60c3ea1c2d75069f9aa7f8d35bc08545a2edb34b/src/PimlicoERC20Paymaster.sol +// Import the required libraries and contracts +import "@account-abstraction/contracts/core/BasePaymaster.sol"; +import "@account-abstraction/contracts/core/Helpers.sol"; +import "@account-abstraction/contracts/interfaces/UserOperation.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +// import "./interfaces/IOracle.sol"; +import "@account-abstraction/contracts/core/EntryPoint.sol"; +// import "./utils/SafeTransferLib.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import {IOracle} from "../Oracle.sol"; +// import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import "./SafeTransferLib.sol"; + +// using SafeERC20 for IERC20; + +/// @title PimlicoERC20Paymaster +/// @notice An ERC-4337 Paymaster contract by Pimlico which is able to sponsor gas fees in exchange for ERC20 tokens. +/// The contract refunds excess tokens if the actual gas cost is lower than the initially provided amount. +/// It also allows updating price configuration and withdrawing tokens by the contract owner. +/// The contract uses an Oracle to fetch the latest token prices. +/// @dev Inherits from BasePaymaster. + +contract PimlicoERC20Paymaster is BasePaymaster { + uint256 public constant priceDenominator = 1e6; + uint256 public constant REFUND_POSTOP_COST = 40000; // Estimated gas cost for refunding tokens after the transaction is completed + + // The token, tokenOracle, and nativeAssetOracle are declared as immutable, + // meaning their values cannot change after contract creation. + IERC20 public immutable token; // The ERC20 token used for transaction fee payments + uint256 public immutable tokenDecimals; + IOracle public immutable tokenOracle; // The Oracle contract used to fetch the latest token prices + IOracle public immutable nativeAssetOracle; // The Oracle contract used to fetch the latest ETH prices + + uint192 public previousPrice; // The cached token price from the Oracle + uint32 public priceMarkup; // The price markup percentage applied to the token price (1e6 = 100%) + uint32 public priceUpdateThreshold; // The price update threshold percentage that triggers a price update (1e6 = 100%) + + event ConfigUpdated(uint32 priceMarkup, uint32 updateThreshold); + + event UserOperationSponsored(address indexed user, uint256 actualTokenNeeded, uint256 actualGasCost); + + /// @notice Initializes the PimlicoERC20Paymaster contract with the given parameters. + /// @param _token The ERC20 token used for transaction fee payments. + /// @param _entryPoint The EntryPoint contract used in the Account Abstraction infrastructure. + /// @param _tokenOracle The Oracle contract used to fetch the latest token prices. + /// @param _nativeAssetOracle The Oracle contract used to fetch the latest native asset (ETH, Matic, Avax, etc.) prices. + /// @param _owner The address that will be set as the owner of the contract. + constructor( + IERC20 _token, + IEntryPoint _entryPoint, + IOracle _tokenOracle, + IOracle _nativeAssetOracle, + address _owner, + uint8 _tokenDecimals + ) BasePaymaster(_entryPoint) { + token = _token; + tokenOracle = _tokenOracle; // oracle for token -> usd + nativeAssetOracle = _nativeAssetOracle; // oracle for native asset(eth/matic/avax..) -> usd + priceMarkup = 110e4; // 110% 1e6 = 100% + priceUpdateThreshold = 25e3; // 2.5% 1e6 = 100% + transferOwnership(_owner); + tokenDecimals = 10 ** _tokenDecimals; + require(_tokenOracle.decimals() == 8, "PP-ERC20 : token oracle decimals must be 8"); + require(_nativeAssetOracle.decimals() == 8, "PP-ERC20 : native asset oracle decimals must be 8"); + } + + /// @notice Updates the price markup and price update threshold configurations. + /// @param _priceMarkup The new price markup percentage (1e6 = 100%). + /// @param _updateThreshold The new price update threshold percentage (1e6 = 100%). + function updateConfig(uint32 _priceMarkup, uint32 _updateThreshold) external onlyOwner { + require(_priceMarkup <= 120e4, "PP-ERC20 : price markup too high"); + require(_priceMarkup >= 1e6, "PP-ERC20 : price markeup too low"); + require(_updateThreshold <= 1e6, "PP-ERC20 : update threshold too high"); + priceMarkup = _priceMarkup; + priceUpdateThreshold = _updateThreshold; + emit ConfigUpdated(_priceMarkup, _updateThreshold); + } + + /// @notice Allows the contract owner to withdraw a specified amount of tokens from the contract. + /// @param to The address to transfer the tokens to. + /// @param amount The amount of tokens to transfer. + function withdrawToken(address to, uint256 amount) external onlyOwner { + token.transfer(to, amount); + } + + /// @notice Updates the token price by fetching the latest price from the Oracle. + function updatePrice() external { + // This function updates the cached ERC20/ETH price ratio + uint192 tokenPrice = fetchPrice(tokenOracle); + uint192 nativeAssetPrice = fetchPrice(nativeAssetOracle); + previousPrice = (nativeAssetPrice * uint192(tokenDecimals)) / tokenPrice; + } + + /// @notice Validates a paymaster user operation and calculates the required token amount for the transaction. + /// @param userOp The user operation data. + /// @param requiredPreFund The amount of tokens required for pre-funding. + /// @return context The context containing the token amount and user sender address (if applicable). + /// @return validationResult A uint256 value indicating the result of the validation (always 0 in this implementation). + function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256 requiredPreFund) + internal + override + returns (bytes memory context, uint256 validationResult) + { + unchecked { + uint256 cachedPrice = previousPrice; + require(cachedPrice != 0, "PP-ERC20 : price not set"); + uint256 length = userOp.paymasterAndData.length - 20; + // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdf is the mask for the last 6 bits 011111 which mean length should be 100000(32) || 000000(0) + require( + length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdf == 0, + "PP-ERC20 : invalid data length" + ); + // NOTE: we assumed that nativeAsset's decimals is 18, if there is any nativeAsset with different decimals, need to change the 1e18 to the correct decimals + uint256 tokenAmount = ( + (requiredPreFund + (REFUND_POSTOP_COST) * userOp.maxFeePerGas) * priceMarkup * cachedPrice + ) / (1e18 * priceDenominator); + if (length == 32) { + require( + tokenAmount <= uint256(bytes32(userOp.paymasterAndData[20:52])), "PP-ERC20 : token amount too high" + ); + } + token.transferFrom(userOp.sender, address(this), tokenAmount); + context = abi.encodePacked(tokenAmount, userOp.sender); + // No return here since validationData == 0 and we have context saved in memory + validationResult = 0; + } + } + + /// @notice Performs post-operation tasks, such as updating the token price and refunding excess tokens. + /// @dev This function is called after a user operation has been executed or reverted. + /// @param mode The post-operation mode (either successful or reverted). + /// @param context The context containing the token amount and user sender address. + /// @param actualGasCost The actual gas cost of the transaction. + function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override { + if (mode == PostOpMode.postOpReverted) { + return; // Do nothing here to not revert the whole bundle and harm reputation + } + unchecked { + uint192 tokenPrice = fetchPrice(tokenOracle); + uint192 nativeAsset = fetchPrice(nativeAssetOracle); + uint256 cachedPrice = previousPrice; + uint192 price = (nativeAsset * uint192(tokenDecimals)) / tokenPrice; + uint256 cachedUpdateThreshold = priceUpdateThreshold; + if ( + (uint256(price) * priceDenominator) / cachedPrice > priceDenominator + cachedUpdateThreshold + || (uint256(price) * priceDenominator) / cachedPrice < priceDenominator - cachedUpdateThreshold + ) { + previousPrice = uint192(int192(price)); + cachedPrice = uint192(int192(price)); + } + // Refund tokens based on actual gas cost + // NOTE: we assumed that nativeAsset's decimals is 18, if there is any nativeAsset with different decimals, need to change the 1e18 to the correct decimals + uint256 actualTokenNeeded = ((actualGasCost + REFUND_POSTOP_COST * tx.gasprice) * priceMarkup * cachedPrice) + / (1e18 * priceDenominator); // We use tx.gasprice here since we don't know the actual gas price used by the user + if (uint256(bytes32(context[0:32])) > actualTokenNeeded) { + // If the initially provided token amount is greater than the actual amount needed, refund the difference + token.transfer(address(bytes20(context[32:52])), uint256(bytes32(context[0:32])) - actualTokenNeeded); + } // If the token amount is not greater than the actual amount needed, no refund occurs + + emit UserOperationSponsored(address(bytes20(context[32:52])), actualTokenNeeded, actualGasCost); + } + } + + /// @notice Fetches the latest price from the given Oracle. + /// @dev This function is used to get the latest price from the tokenOracle or nativeAssetOracle. + /// @param _oracle The Oracle contract to fetch the price from. + /// @return price The latest price fetched from the Oracle. + function fetchPrice(IOracle _oracle) internal view returns (uint192 price) { + (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = _oracle.latestRoundData(); + require(answer > 0, "PP-ERC20 : Chainlink price <= 0"); + // 2 days old price is considered stale since the price is updated every 24 hours + require(updatedAt >= block.timestamp - 60 * 60 * 24 * 2, "PP-ERC20 : Incomplete round"); + require(answeredInRound >= roundId, "PP-ERC20 : Stale price"); + price = uint192(int192(answer)); + } +} diff --git a/src/paymasters/AccountAbstraction/SafeTransferLib.sol b/src/paymasters/AccountAbstraction/SafeTransferLib.sol new file mode 100644 index 00000000..72c25bbc --- /dev/null +++ b/src/paymasters/AccountAbstraction/SafeTransferLib.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +// From: https://github.com/pimlicolabs/erc20-paymaster-contracts/blob/60c3ea1c2d75069f9aa7f8d35bc08545a2edb34b/src/utils/SafeTransferLib.sol +// Note: this is used instead of openzeppelin to avoid selfbalance opcodes + +/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) +/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller. +library SafeTransferLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ETH transfer has failed. + error ETHTransferFailed(); + + /// @dev The ERC20 `transferFrom` has failed. + error TransferFromFailed(); + + /// @dev The ERC20 `transfer` has failed. + error TransferFailed(); + + /// @dev The ERC20 `approve` has failed. + error ApproveFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Suggested gas stipend for contract receiving ETH + /// that disallows any storage writes. + uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300; + + /// @dev Suggested gas stipend for contract receiving ETH to perform a few + /// storage reads and writes, but low enough to prevent griefing. + /// Multiply by a small constant (e.g. 2), if needed. + uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC20 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. + /// Reverts upon failure. + /// + /// The `from` account must have at least `amount` approved for + /// the current contract to manage. + function safeTransferFrom(address token, address from, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + + mstore(0x60, amount) // Store the `amount` argument. + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + // Store the function selector of `transferFrom(address,address,uint256)`. + mstore(0x0c, 0x23b872dd000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFromFailed()`. + mstore(0x00, 0x7939f424) + // Revert with (offset, size). + revert(0x00, 0x20) + } + + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends all of ERC20 `token` from `from` to `to`. + /// Reverts upon failure. + /// + /// The `from` account must have at least `amount` approved for + /// the current contract to manage. + function safeTransferAllFrom(address token, address from, address to) internal returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + // Store the function selector of `balanceOf(address)`. + mstore(0x0c, 0x70a08231000000000000000000000000) + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) + ) + ) { + // Store the function selector of `TransferFromFailed()`. + mstore(0x00, 0x7939f424) + // Revert with (offset, size). + revert(0x00, 0x20) + } + + // Store the function selector of `transferFrom(address,address,uint256)`. + mstore(0x00, 0x23b872dd) + // The `amount` argument is already written to the memory word at 0x6c. + amount := mload(0x60) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFromFailed()`. + mstore(0x00, 0x7939f424) + // Revert with (offset, size). + revert(0x00, 0x20) + } + + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. + /// Reverts upon failure. + function safeTransfer(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + // Store the function selector of `transfer(address,uint256)`. + mstore(0x00, 0xa9059cbb000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFailed()`. + mstore(0x00, 0x90b8ec18) + // Revert with (offset, size). + revert(0x00, 0x20) + } + // Restore the part of the free memory pointer that was overwritten. + mstore(0x34, 0) + } + } + + /// @dev Sends all of ERC20 `token` from the current contract to `to`. + /// Reverts upon failure. + function safeTransferAll(address token, address to) internal returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. + mstore(0x20, address()) // Store the address of the current contract. + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) + ) + ) { + // Store the function selector of `TransferFailed()`. + mstore(0x00, 0x90b8ec18) + // Revert with (offset, size). + revert(0x00, 0x20) + } + + mstore(0x14, to) // Store the `to` argument. + // The `amount` argument is already written to the memory word at 0x34. + amount := mload(0x34) + // Store the function selector of `transfer(address,uint256)`. + mstore(0x00, 0xa9059cbb000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFailed()`. + mstore(0x00, 0x90b8ec18) + // Revert with (offset, size). + revert(0x00, 0x20) + } + // Restore the part of the free memory pointer that was overwritten. + mstore(0x34, 0) + } + } + + /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. + /// Reverts upon failure. + function safeApprove(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + // Store the function selector of `approve(address,uint256)`. + mstore(0x00, 0x095ea7b3000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + ) + ) { + // Store the function selector of `ApproveFailed()`. + mstore(0x00, 0x3e3f8f73) + // Revert with (offset, size). + revert(0x00, 0x20) + } + // Restore the part of the free memory pointer that was overwritten. + mstore(0x34, 0) + } + } + + /// @dev Returns the amount of ERC20 `token` owned by `account`. + /// Returns zero if the `token` does not exist. + function balanceOf(address token, address account) internal view returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, account) // Store the `account` argument. + // Store the function selector of `balanceOf(address)`. + mstore(0x00, 0x70a08231000000000000000000000000) + amount := + mul( + mload(0x20), + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) + ) + ) + } + } +} diff --git a/src/paymasters/OnboardingPaymaster.sol b/src/paymasters/OnboardingPaymaster.sol new file mode 100644 index 00000000..2c85dd61 --- /dev/null +++ b/src/paymasters/OnboardingPaymaster.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier:MIT +pragma solidity ^0.8.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "gsn/packages/contracts/src/forwarder/IForwarder.sol"; +import "gsn/packages/contracts/src/BasePaymaster.sol"; + +contract OnboardingPaymaster is BasePaymaster { + address public whitelistedContract; + uint32 public whitelistedSelector; + uint256 public gasUsedByPost; + + constructor(address _whitelistedContract, uint32 _whitelistedSelector) { + whitelistedContract = _whitelistedContract; + whitelistedSelector = _whitelistedSelector; + } + + event PreRelay(address indexed sender); + event PostRelay(address indexed sender); + + function versionPaymaster() external view virtual override returns (string memory) { + return "3.0.0-beta.10+opengsn.oracle.token.ipaymaster"; + } + + function setPostGasUsage(uint256 _gasUsedByPost) external onlyOwner { + gasUsedByPost = _gasUsedByPost; + } + + function _getPaymasterData(bytes memory paymasterData) private returns (IERC20 token, uint256 maxTokens) { + (address tokenAddress, uint256 _maxTokens) = abi.decode(paymasterData, (address, uint256)); + + maxTokens = _maxTokens; + token = IERC20(tokenAddress); + } + + function getSelector(bytes calldata call) public view returns (uint32) { + uint32 ret = uint32(bytes4(call[0:4])); + return ret; + } + + function _preRelayedCall( + GsnTypes.RelayRequest calldata relayRequest, + bytes calldata signature, + bytes calldata approvalData, + uint256 maxPossibleGas + ) internal virtual override returns (bytes memory context, bool revertOnRecipientRevert) { + require(relayRequest.request.to == whitelistedContract, "Recipient is not whitelisted"); + + uint32 selector = getSelector(relayRequest.request.data); + + require(selector == whitelistedSelector, "Selector is not whitelisted"); + + emit PreRelay(relayRequest.request.from); + + return (abi.encode(relayRequest.request.from), false); + } + + function _postRelayedCall( + bytes calldata context, + bool, + uint256 gasUseWithoutPost, + GsnTypes.RelayData calldata relayData + ) internal virtual override { + address from = abi.decode(context, (address)); + emit PostRelay(from); + } +} diff --git a/src/paymasters/Oracle.sol b/src/paymasters/Oracle.sol new file mode 100644 index 00000000..cb591fc0 --- /dev/null +++ b/src/paymasters/Oracle.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier:MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IOracle { + function decimals() external view returns (uint8); + + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); +} + +contract DummyOracle is IOracle { + int256 public price; + + function decimals() external view returns (uint8) { + return 8; + } + + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { + roundId = 0; + answer = price; + startedAt = 0; + updatedAt = block.timestamp; // pretend we just got the block + answeredInRound = 0; + } + + function setPrice(int256 _price) public { + price = _price; + } +} diff --git a/src/paymasters/OracleTokenPaymaster.sol b/src/paymasters/OracleTokenPaymaster.sol new file mode 100644 index 00000000..a355d9c8 --- /dev/null +++ b/src/paymasters/OracleTokenPaymaster.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier:MIT +pragma solidity ^0.8.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "gsn/packages/contracts/src/forwarder/IForwarder.sol"; +import "gsn/packages/contracts/src/BasePaymaster.sol"; + +import {IOracle} from "./Oracle.sol"; + +// Based on https://github.com/opengsn/gsn/blob/v3.0.0-beta.10/packages/paymasters/contracts/TokenPaymaster.sol +// But modified to use an oracle rather than uniswap. + +/// A very basic paymaster that makes the payer pay in ERC20 tokens. +/// - The token prices need to be provided by an IOracle. +/// - No swaps are done - the paymaster simply receives ERC20 tokens. This means +/// that over time, the paymaster's eth balance will decrease. It is up to the +/// owner of the contract to replenish the eth balance. +/// - The owner of the contract can withdraw their received erc20 balances. +/// - Users specify an erc20 address and a maximum amount they are willing to pay +/// for the tx. This reduces the trust put in the oracle. +contract OracleTokenPaymaster is BasePaymaster { + IOracle nativeTokenOracle; + mapping(IERC20 => TokenDetails) public tokenOracles; + uint256 public gasUsedByPost; + + struct TokenDetails { + uint256 div; // the scaling factor of the token (NOT oracle). Usually 1e18 + IOracle oracle; + } + + event PreRelayPayment(uint256 ethAmount, IERC20 token, uint256 tokenAmount, address indexed payer); + event PostRelay(uint256 actualEthAmount, IERC20 token, uint256 actualTokenAmount, address payer); + + constructor(IOracle _nativeTokenOracle) { + require(_nativeTokenOracle.decimals() == 8, "OTP: native token oracle decimals must be 8"); + + nativeTokenOracle = _nativeTokenOracle; + } + + function versionPaymaster() external view virtual override returns (string memory) { + return "3.0.0-beta.10+opengsn.oracle.token.ipaymaster"; + } + + function addOracle(IERC20 _token, uint256 _decimals, IOracle _oracle) external onlyOwner { + require(_oracle.decimals() == 8, "OTP: token oracle decimals must be 8"); + tokenOracles[_token] = TokenDetails({div: 10 ** _decimals, oracle: _oracle}); + } + + function fetchPrice(IOracle _oracle) internal view returns (uint192 price) { + (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = _oracle.latestRoundData(); + require(answer > 0, "OTP : Chainlink price <= 0"); + // 2 days old price is considered stale since the price is updated every 24 hours + require(updatedAt >= block.timestamp - 60 * 60 * 24 * 2, "OTP : Incomplete round"); + require(answeredInRound >= roundId, "OTP : Stale price"); + price = uint192(int192(answer)); + } + + function _ethToTokens(IERC20 token, uint256 ethAmount) internal view returns (uint256) { + TokenDetails memory _tokenOracle = tokenOracles[token]; + require(address(_tokenOracle.oracle) != address(0x00), "OTP: Oracle does not exist"); + + uint192 tokenPrice = fetchPrice(_tokenOracle.oracle); // #decimals: 8 + uint192 nativeAssetPrice = fetchPrice(nativeTokenOracle); // #decimals: 8 + uint192 relativePrice = (nativeAssetPrice * uint192(_tokenOracle.div)) / tokenPrice; // #decimals: _tokenOracle.div + + // #decimals: 18 + _tokenOracle.div - 18 = _tokenOracle.div + uint256 tokenAmount = (ethAmount * relativePrice) / 1e18; + + return tokenAmount; + } + + function setPostGasUsage(uint256 _gasUsedByPost) external onlyOwner { + gasUsedByPost = _gasUsedByPost; + } + + function withdrawAll(IERC20 token) external onlyOwner { + uint256 balance = token.balanceOf(address(this)); + token.transfer(msg.sender, balance); + } + + function getPayer(GsnTypes.RelayRequest calldata relayRequest) public view virtual returns (address) { + (this); + return relayRequest.request.from; + } + + function _getPaymasterData(bytes memory paymasterData) private returns (IERC20 token, uint256 maxTokens) { + (address tokenAddress, uint256 _maxTokens) = abi.decode(paymasterData, (address, uint256)); + + maxTokens = _maxTokens; + token = IERC20(tokenAddress); + } + + function _calculatePreCharge(IERC20 token, GsnTypes.RelayRequest calldata relayRequest, uint256 maxPossibleGas) + internal + returns (address payer, uint256 ethPrecharge, uint256 tokenPreCharge) + { + payer = this.getPayer(relayRequest); + uint256 ethMaxCharge = relayHub.calculateCharge(maxPossibleGas, relayRequest.relayData); + ethMaxCharge += relayRequest.request.value; + tokenPreCharge = _ethToTokens(token, ethMaxCharge); + ethPrecharge = ethMaxCharge; + } + + function _verifyPaymasterData(GsnTypes.RelayRequest calldata relayRequest) internal view virtual override { + require(relayRequest.relayData.paymasterData.length == 64, "paymasterData: invalid length"); + } + + function __preRelayedCall( + GsnTypes.RelayRequest calldata relayRequest, + bytes calldata signature, + bytes calldata approvalData, + uint256 maxPossibleGas + ) public returns (bytes memory context, bool revertOnRecipientRevert) { + _preRelayedCall(relayRequest, signature, approvalData, maxPossibleGas); + } + + function _preRelayedCall( + GsnTypes.RelayRequest calldata relayRequest, + bytes calldata signature, + bytes calldata approvalData, + uint256 maxPossibleGas + ) internal virtual override returns (bytes memory context, bool revertOnRecipientRevert) { + (IERC20 token, uint256 maxTokens) = _getPaymasterData(relayRequest.relayData.paymasterData); + (address payer, uint256 ethPrecharge, uint256 tokenPrecharge) = + _calculatePreCharge(token, relayRequest, maxPossibleGas); + + require(tokenPrecharge <= maxTokens, "Tx cost more than the user-supplied limit"); + + token.transferFrom(payer, address(this), tokenPrecharge); + + emit PreRelayPayment(ethPrecharge, token, tokenPrecharge, payer); + + return (abi.encode(payer, tokenPrecharge, token), false); + } + + function _postRelayedCall( + bytes calldata context, + bool, + uint256 gasUseWithoutPost, + GsnTypes.RelayData calldata relayData + ) internal virtual override { + (address payer, uint256 tokenPrecharge, IERC20 token) = abi.decode(context, (address, uint256, IERC20)); + _postRelayedCallInternal(payer, tokenPrecharge, 0, gasUseWithoutPost, relayData, token); + } + + function _postRelayedCallInternal( + address payer, + uint256 tokenPrecharge, + uint256 valueRequested, + uint256 gasUseWithoutPost, + GsnTypes.RelayData calldata relayData, + IERC20 token + ) internal { + uint256 ethActualCharge = relayHub.calculateCharge(gasUseWithoutPost + gasUsedByPost, relayData); + uint256 tokenActualCharge = _ethToTokens(token, valueRequested + ethActualCharge); + uint256 tokenRefund = tokenPrecharge - tokenActualCharge; + + emit PostRelay(ethActualCharge, token, tokenActualCharge, payer); + + require(token.transfer(payer, tokenRefund), "failed refund"); + } +} diff --git a/src/relay/TestLightRelay.sol b/src/relay/TestLightRelay.sol index c77e2b74..c5d1085d 100644 --- a/src/relay/TestLightRelay.sol +++ b/src/relay/TestLightRelay.sol @@ -21,7 +21,7 @@ contract TestLightRelay is LightRelay { /// @notice Sets the current and previous difficulty based on the difficulty /// inferred from the provided Bitcoin headers. - function setDifficultyFromHeaders(bytes memory bitcoinHeaders) external onlyOwner { + function setDifficultyFromHeaders(bytes memory bitcoinHeaders) external { uint256 firstHeaderDiff = bitcoinHeaders.extractTarget().calculateDifficulty(); currentEpochDifficulty = firstHeaderDiff; diff --git a/src/swap/Bridge.sol b/src/swap/Bridge.sol index 11508243..a2aa8195 100644 --- a/src/swap/Bridge.sol +++ b/src/swap/Bridge.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.13; import {BobWrappedBtc} from "./Wrapped.sol"; +import {ERC2771Recipient} from "@opengsn/packages/contracts/src/ERC2771Recipient.sol"; -contract Bridge { +contract Bridge is ERC2771Recipient { uint256 public number; uint256 public collateralThreshold; uint256 nextOrderId; @@ -38,16 +39,16 @@ contract Bridge { function mint() public payable { uint256 collateral = msg.value; // this is the amount of eth sent to the contract uint256 mintedAmount = colToBtc(collateral) / collateralThreshold; - wrapped.sudoMint(msg.sender, mintedAmount); + wrapped.sudoMint(_msgSender(), mintedAmount); - suppliedCollateral[msg.sender] += collateral; + suppliedCollateral[_msgSender()] += collateral; totalCollateral += collateral; } /// request zBTC to be redeemed for given amount of BTC. function requestSwap(uint256 amountZbtc, uint256 amountBtc, BitcoinAddress calldata bitcoinAddress) public { // lock Zbtc by transfering it to the contract address - wrapped.sudoTransferFrom(msg.sender, address(this), amountZbtc); + wrapped.sudoTransferFrom(_msgSender(), address(this), amountZbtc); require(amountZbtc != 0); uint256 id = nextOrderId++; @@ -55,7 +56,7 @@ contract Bridge { open: true, amountZbtc: amountZbtc, amountBtc: amountBtc, - requesterAddress: msg.sender, + requesterAddress: _msgSender(), accepterAddress: address(0), bitcoinAddress: bitcoinAddress }); @@ -68,13 +69,13 @@ contract Bridge { // todo: protocol should probably require some sort of collateral deposit here order.open = false; - order.accepterAddress = msg.sender; + order.accepterAddress = _msgSender(); } // not documented, but presumably required function cancelSwap(uint256 id) public { Order storage order = orders[id]; - require(order.requesterAddress == msg.sender); + require(order.requesterAddress == _msgSender()); // ensure the request was not accepted yet require(order.accepterAddress == address(0)); @@ -99,11 +100,11 @@ contract Bridge { function liquidate(uint256 amountZbtc) public { // burn the zbtc erc20 - wrapped.sudoBurnFrom(msg.sender, amountZbtc); + wrapped.sudoBurnFrom(_msgSender(), amountZbtc); // transfer eth to caller uint256 collateral = btcToCol(amountZbtc); - address payable callerAddress = payable(msg.sender); + address payable callerAddress = payable(_msgSender()); callerAddress.transfer(collateral); } @@ -111,8 +112,8 @@ contract Bridge { uint256 totalZbtc = wrapped.totalSupply(); uint256 requiredCol = btcToCol(totalZbtc * collateralThreshold); uint256 colFree = totalCollateral - requiredCol; - uint256 withdrawal = colFree < suppliedCollateral[msg.sender] ? colFree : suppliedCollateral[msg.sender]; - suppliedCollateral[msg.sender] -= withdrawal; + uint256 withdrawal = colFree < suppliedCollateral[_msgSender()] ? colFree : suppliedCollateral[_msgSender()]; + suppliedCollateral[_msgSender()] -= withdrawal; totalCollateral -= withdrawal; } diff --git a/src/swap/Btc_Marketplace.sol b/src/swap/Btc_Marketplace.sol index b4ed8dc3..798794f8 100644 --- a/src/swap/Btc_Marketplace.sol +++ b/src/swap/Btc_Marketplace.sol @@ -4,12 +4,14 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {BTCUtils} from "@bob-collective/bitcoin-spv/BTCUtils.sol"; import {BitcoinTx} from "../bridge/BitcoinTx.sol"; +import {ERC2771Recipient} from "@opengsn/packages/contracts/src/ERC2771Recipient.sol"; import {IRelay} from "../bridge/IRelay.sol"; +import {TestLightRelay} from "../relay/TestLightRelay.sol"; import {BridgeState} from "../bridge/BridgeState.sol"; using SafeERC20 for IERC20; -contract BtcMarketPlace { +contract BtcMarketPlace is ERC2771Recipient { using BitcoinTx for BridgeState.Storage; mapping(uint256 => BtcBuyOrder) public btcBuyOrders; @@ -21,10 +23,13 @@ contract BtcMarketPlace { uint256 public constant REQUEST_EXPIRATION_SECONDS = 6 hours; BridgeState.Storage internal relay; + TestLightRelay internal testLightRelay; - constructor(IRelay _relay) { + constructor(IRelay _relay, address erc2771Forwarder) { + _setTrustedForwarder(erc2771Forwarder); relay.relay = _relay; relay.txProofDifficultyFactor = 1; // will make this an arg later on + testLightRelay = TestLightRelay(address(relay.relay)); } function setRelay(IRelay _relay) internal { @@ -111,7 +116,7 @@ contract BtcMarketPlace { amountBtc: amountBtc, askingToken: buyingToken, askingAmount: buyAmount, - requester: msg.sender + requester: _msgSender() }); emit placeBtcSellOrderEvent(id, amountBtc, buyingToken, buyAmount); @@ -133,7 +138,7 @@ contract BtcMarketPlace { order.amountBtc -= amountBtc; // "lock" selling token by transferring to contract - IERC20(order.askingToken).safeTransferFrom(msg.sender, address(this), sellAmount); + IERC20(order.askingToken).safeTransferFrom(_msgSender(), address(this), sellAmount); uint256 acceptId = nextOrderId++; acceptedBtcSellOrders[acceptId] = AcceptedBtcSellOrder({ @@ -143,7 +148,7 @@ contract BtcMarketPlace { ercToken: order.askingToken, ercAmount: sellAmount, requester: order.requester, - accepter: msg.sender, + accepter: _msgSender(), acceptTime: block.timestamp }); @@ -156,8 +161,10 @@ contract BtcMarketPlace { public { AcceptedBtcSellOrder storage accept = acceptedBtcSellOrders[id]; - require(accept.requester == msg.sender); + require(accept.requester == _msgSender()); + + testLightRelay.setDifficultyFromHeaders(proof.bitcoinHeaders); relay.validateProof(transaction, proof); _checkBitcoinTxOutput(accept.amountBtc, accept.bitcoinAddress, transaction); @@ -171,7 +178,7 @@ contract BtcMarketPlace { function withdrawBtcSellOrder(uint256 id) public { BtcSellOrder storage order = btcSellOrders[id]; - require(order.requester == msg.sender); + require(order.requester == _msgSender()); delete btcSellOrders[id]; @@ -183,9 +190,9 @@ contract BtcMarketPlace { require(block.timestamp > order.acceptTime + REQUEST_EXPIRATION_SECONDS); - require(order.accepter == msg.sender); + require(order.accepter == _msgSender()); // give accepter its tokens back - IERC20(order.ercToken).safeTransfer(msg.sender, order.ercAmount); + IERC20(order.ercToken).safeTransfer(_msgSender(), order.ercAmount); delete acceptedBtcSellOrders[id]; @@ -201,7 +208,7 @@ contract BtcMarketPlace { require(sellingToken != address(0x0)); // "lock" selling token by transferring to contract - IERC20(sellingToken).safeTransferFrom(msg.sender, address(this), saleAmount); + IERC20(sellingToken).safeTransferFrom(_msgSender(), address(this), saleAmount); uint256 id = nextOrderId++; btcBuyOrders[id] = BtcBuyOrder({ @@ -209,7 +216,7 @@ contract BtcMarketPlace { bitcoinAddress: bitcoinAddress, offeringToken: sellingToken, offeringAmount: saleAmount, - requester: msg.sender + requester: _msgSender() }); emit placeBtcBuyOrderEvent(amountBtc, bitcoinAddress, sellingToken, saleAmount); @@ -236,7 +243,7 @@ contract BtcMarketPlace { ercToken: order.offeringToken, ercAmount: buyAmount, requester: order.requester, - accepter: msg.sender, + accepter: _msgSender(), acceptTime: block.timestamp }); @@ -252,8 +259,9 @@ contract BtcMarketPlace { function proofBtcBuyOrder(uint256 id, BitcoinTx.Info calldata transaction, BitcoinTx.Proof calldata proof) public { AcceptedBtcBuyOrder storage accept = acceptedBtcBuyOrders[id]; - require(accept.accepter == msg.sender); + require(accept.accepter == _msgSender()); + testLightRelay.setDifficultyFromHeaders(proof.bitcoinHeaders); relay.validateProof(transaction, proof); BtcBuyOrder storage order = btcBuyOrders[accept.orderId]; @@ -269,10 +277,10 @@ contract BtcMarketPlace { function withdrawBtcBuyOrder(uint256 id) public { BtcBuyOrder storage order = btcBuyOrders[id]; - require(order.requester == msg.sender); + require(order.requester == _msgSender()); // release the locked erc20s - IERC20(order.offeringToken).safeTransfer(msg.sender, order.offeringAmount); + IERC20(order.offeringToken).safeTransfer(_msgSender(), order.offeringAmount); delete btcBuyOrders[id]; @@ -282,12 +290,12 @@ contract BtcMarketPlace { function cancelAcceptedBtcBuyOrder(uint256 id) public { AcceptedBtcBuyOrder storage accept = acceptedBtcBuyOrders[id]; - require(accept.requester == msg.sender); + require(accept.requester == _msgSender()); require(block.timestamp > accept.acceptTime + REQUEST_EXPIRATION_SECONDS); // release the locked erc20s - IERC20(accept.ercToken).safeTransfer(msg.sender, accept.ercAmount); + IERC20(accept.ercToken).safeTransfer(_msgSender(), accept.ercAmount); // note: we don't make the accepted amount available for new trades but if we want to, // we could implement that diff --git a/src/swap/Faucet.sol b/src/swap/Faucet.sol new file mode 100644 index 00000000..1fc1c226 --- /dev/null +++ b/src/swap/Faucet.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Ownable, Context} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC2771Recipient} from "@opengsn/packages/contracts/src/ERC2771Recipient.sol"; + +interface Erc20Mintable { + function decimals() external returns (uint256); + + function mint(uint256 amount) external; + + function transfer(address to, uint256 value) external returns (bool); +} + +contract Faucet is Ownable, ERC2771Recipient { + uint256 nextTokenId; + mapping(uint256 => address) supportedErc20Addresses; + + function addErc20(address newErc20) public onlyOwner { + supportedErc20Addresses[nextTokenId++] = newErc20; + } + + // Mints 30000 of each erc20 + function mint() public { + for (uint256 id = 0; id < nextTokenId; id++) { + Erc20Mintable token = Erc20Mintable(supportedErc20Addresses[id]); + uint256 amount = 30000 * (10 ** token.decimals()); + token.mint(amount); + token.transfer(_msgSender(), amount); + } + } + + function _msgSender() internal view override(Context, ERC2771Recipient) returns (address sender) { + sender = ERC2771Recipient._msgSender(); + } + + function _msgData() internal view override(Context, ERC2771Recipient) returns (bytes calldata) { + return ERC2771Recipient._msgData(); + } +} diff --git a/src/swap/Marketplace.sol b/src/swap/Marketplace.sol index 43f24584..6619398d 100644 --- a/src/swap/Marketplace.sol +++ b/src/swap/Marketplace.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ERC2771Recipient} from "@opengsn/packages/contracts/src/ERC2771Recipient.sol"; using SafeERC20 for IERC20; -contract MarketPlace { +contract MarketPlace is ERC2771Recipient { mapping(uint256 => Order) public ercErcOrders; // cant have struct as key, nor tupple event placeOrder( @@ -29,6 +30,10 @@ contract MarketPlace { address requesterAddress; } + constructor(address erc2771Forwarder) { + _setTrustedForwarder(erc2771Forwarder); + } + function placeErcErcOrder(address sellingToken, uint256 saleAmount, address buyingToken, uint256 buyAmount) public { @@ -36,7 +41,7 @@ contract MarketPlace { require(buyingToken != address(0x0)); // "lock" selling token by transferring to contract - IERC20(sellingToken).safeTransferFrom(msg.sender, address(this), saleAmount); + IERC20(sellingToken).safeTransferFrom(_msgSender(), address(this), saleAmount); uint256 id = nextOrderId++; Order memory order = Order({ @@ -44,7 +49,7 @@ contract MarketPlace { offeringToken: sellingToken, askingAmount: buyAmount, askingToken: buyingToken, - requesterAddress: msg.sender + requesterAddress: _msgSender() }); ercErcOrders[id] = order; @@ -71,24 +76,24 @@ contract MarketPlace { ercErcOrders[id].offeringAmount -= buyAmount; ercErcOrders[id].askingAmount -= saleAmount; - IERC20(order.askingToken).safeTransferFrom(msg.sender, order.requesterAddress, saleAmount); - IERC20(order.offeringToken).safeTransfer(msg.sender, buyAmount); + IERC20(order.askingToken).safeTransferFrom(_msgSender(), order.requesterAddress, saleAmount); + IERC20(order.offeringToken).safeTransfer(_msgSender(), buyAmount); - emit acceptOrder(id, msg.sender, buyAmount, saleAmount); + emit acceptOrder(id, _msgSender(), buyAmount, saleAmount); } function withdrawErcErcOrder(uint256 id) public { Order memory order = ercErcOrders[id]; - require(order.requesterAddress == msg.sender); + require(order.requesterAddress == _msgSender()); - IERC20(order.offeringToken).safeTransfer(msg.sender, order.offeringAmount); + IERC20(order.offeringToken).safeTransfer(_msgSender(), order.offeringAmount); delete ercErcOrders[id]; emit withdrawOrder(id); } - function getOpenOrders() external view returns (Order[] memory) { + function getOpenOrders() external view returns (Order[] memory, uint256[] memory) { uint256 numOpenOrders = 0; for (uint256 i = 0; i < nextOrderId; i++) { if (ercErcOrders[i].offeringAmount > 0) { @@ -96,13 +101,15 @@ contract MarketPlace { } } Order[] memory ret = new Order[](numOpenOrders); + uint256[] memory identifiers = new uint256[](numOpenOrders); uint256 numPushed = 0; for (uint256 i = 0; i < nextOrderId; i++) { if (ercErcOrders[i].offeringAmount > 0) { ret[numPushed] = ercErcOrders[i]; + identifiers[numPushed] = i; numPushed++; } } - return ret; + return (ret, identifiers); } } diff --git a/src/swap/Ord_Marketplace.sol b/src/swap/Ord_Marketplace.sol new file mode 100644 index 00000000..743c8c75 --- /dev/null +++ b/src/swap/Ord_Marketplace.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {BTCUtils} from "@bob-collective/bitcoin-spv/BTCUtils.sol"; +import {BitcoinTx} from "../bridge/BitcoinTx.sol"; +import {IRelay} from "../bridge/IRelay.sol"; +import {BridgeState} from "../bridge/BridgeState.sol"; +import {TestLightRelay} from "../relay/TestLightRelay.sol"; +import "forge-std/console.sol"; + +using SafeERC20 for IERC20; + +contract OrdMarketplace { + using BitcoinTx for BridgeState.Storage; + + mapping(uint256 => OrdinalSellOrder) public ordinalSellOrders; + mapping(uint256 => AcceptedOrdinalSellOrder) public acceptedOrdinalSellOrders; + + uint256 nextOrdinalId; + uint256 public constant REQUEST_EXPIRATION_SECONDS = 6 hours; + + BridgeState.Storage internal relay; + TestLightRelay internal testLightRelay; + + constructor(IRelay _relay) { + relay.relay = _relay; + relay.txProofDifficultyFactor = 1; // will make this an arg later on + testLightRelay = TestLightRelay(address(relay.relay)); + } + + function setRelay(IRelay _relay) internal { + relay.relay = _relay; + } + + event placeOrdinalSellOrderEvent( + uint256 indexed orderId, OrdinalId ordinalID, address sellToken, uint256 sellAmount + ); + event acceptOrdinalSellOrderEvent( + uint256 indexed id, uint256 indexed acceptId, BitcoinAddress bitcoinAddress, address ercToken, uint256 ercAmount + ); + event proofOrdinalSellOrderEvent(uint256 id); + event withdrawOrdinalSellOrderEvent(uint256 id); + event cancelAcceptedOrdinalSellOrderEvent(uint256 id); + + struct OrdinalSellOrder { + OrdinalId ordinalID; + address sellToken; + uint256 sellAmount; + BitcoinTx.UTXO utxo; + address requester; + bool isOrderAccepted; + } + + struct AcceptedOrdinalSellOrder { + uint256 orderId; + BitcoinAddress bitcoinAddress; + address ercToken; + uint256 ercAmount; + address requester; + address acceptor; + uint256 acceptTime; + } + + struct BitcoinAddress { + bytes scriptPubKey; + } + + struct OrdinalId { + bytes32 txId; + uint32 index; + } + + function placeOrdinalSellOrder( + OrdinalId calldata ordinalID, + BitcoinTx.UTXO calldata utxo, + address sellToken, + uint256 sellAmount + ) public { + require(sellToken != address(0x0), "Invalid buying token"); + require(sellAmount > 0, "Buying amount should be greater than 0"); + + uint256 id = nextOrdinalId++; + + ordinalSellOrders[id] = OrdinalSellOrder({ + ordinalID: ordinalID, + sellToken: sellToken, + sellAmount: sellAmount, + utxo: utxo, + requester: msg.sender, + isOrderAccepted: false + }); + + emit placeOrdinalSellOrderEvent(id, ordinalID, sellToken, sellAmount); + } + + function acceptOrdinalSellOrder(uint256 id, BitcoinAddress calldata bitcoinAddress) public returns (uint256) { + OrdinalSellOrder storage order = ordinalSellOrders[id]; + require(order.isOrderAccepted == false, "Order Already Accepted"); + // "lock" sell token by transferring to contract + IERC20(order.sellToken).safeTransferFrom(msg.sender, address(this), order.sellAmount); + + uint256 acceptId = nextOrdinalId++; + + acceptedOrdinalSellOrders[acceptId] = AcceptedOrdinalSellOrder({ + orderId: id, + bitcoinAddress: bitcoinAddress, + ercToken: order.sellToken, + ercAmount: order.sellAmount, + requester: order.requester, + acceptor: msg.sender, + acceptTime: block.timestamp + }); + + order.isOrderAccepted = true; + + emit acceptOrdinalSellOrderEvent(id, acceptId, bitcoinAddress, order.sellToken, order.sellAmount); + + return acceptId; + } + + function proofOrdinalSellOrder(uint256 id, BitcoinTx.Info calldata transaction, BitcoinTx.Proof calldata proof) + public + { + AcceptedOrdinalSellOrder storage accept = acceptedOrdinalSellOrders[id]; + require(accept.requester == msg.sender, "Sender not the requester"); + + OrdinalSellOrder storage order = ordinalSellOrders[accept.orderId]; + + testLightRelay.setDifficultyFromHeaders(proof.bitcoinHeaders); + relay.validateProof(transaction, proof); + + BitcoinTx.ensureTxInputSpendsUtxo(transaction.inputVector, order.utxo); + + // check if output to the buyer's address + _checkBitcoinTxOutput(accept.bitcoinAddress, transaction); + + // ToDo: check that the correct satoshi is being spent to the buyer's address + + IERC20(accept.ercToken).safeTransfer(accept.requester, accept.ercAmount); + + delete ordinalSellOrders[accept.orderId]; + delete acceptedOrdinalSellOrders[id]; + emit proofOrdinalSellOrderEvent(id); + } + + /** + * Checks output script pubkey (recipient address) matches output script. + * Reverts if bitcoin address is not found. + * + * @param bitcoinAddress Recipient's bitcoin address. + * @param transaction Transaction fulfilling the order. + */ + function _checkBitcoinTxOutput(BitcoinAddress storage bitcoinAddress, BitcoinTx.Info calldata transaction) + private + { + // Prefixes scriptpubkey with its size to match script output data. + bytes32 scriptPubKeyHash = + keccak256(abi.encodePacked(uint8(bitcoinAddress.scriptPubKey.length), bitcoinAddress.scriptPubKey)); + + // it will revert if no match for scriptPubKeyHash found in any outputScriptHash + BitcoinTx.getTxOutputValue(scriptPubKeyHash, transaction.outputVector); + } + + function withdrawOrdinalSellOrder(uint256 id) public { + OrdinalSellOrder storage order = ordinalSellOrders[id]; + + require(order.requester == msg.sender, "Sender not the requester"); + require(order.isOrderAccepted == false, "Order has already been accepted, cannot withdraw"); + + delete ordinalSellOrders[id]; + + emit withdrawOrdinalSellOrderEvent(id); + } + + function cancelAcceptedOrdinalSellOrder(uint256 id) public { + AcceptedOrdinalSellOrder storage order = acceptedOrdinalSellOrders[id]; + + require(block.timestamp > order.acceptTime + REQUEST_EXPIRATION_SECONDS, "Request still valid"); + + require(order.acceptor == msg.sender, "Sender not the acceptor"); + + // give acceptor its tokens back + IERC20(order.ercToken).safeTransfer(msg.sender, order.ercAmount); + + delete acceptedOrdinalSellOrders[id]; + + emit cancelAcceptedOrdinalSellOrderEvent(id); + } + + function getOpenOrdinalSellOrders() external view returns (OrdinalSellOrder[] memory, uint256[] memory) { + uint256 numOpenOrders = 0; + for (uint256 i = 0; i < nextOrdinalId; i++) { + if (ordinalSellOrders[i].requester != address(0x0)) { + numOpenOrders++; + } + } + + OrdinalSellOrder[] memory ret = new OrdinalSellOrder[](numOpenOrders); + uint256[] memory identifiers = new uint256[](numOpenOrders); + uint256 numPushed = 0; + for (uint256 i = 0; i < nextOrdinalId; i++) { + if (ordinalSellOrders[i].requester != address(0x0)) { + ret[numPushed] = ordinalSellOrders[i]; + identifiers[numPushed] = i; + numPushed++; + } + } + return (ret, identifiers); + } + + function getOpenAcceptedOrdinalSellOrders() + external + view + returns (AcceptedOrdinalSellOrder[] memory, uint256[] memory) + { + uint256 numOpenOrders = 0; + for (uint256 i = 0; i < nextOrdinalId; i++) { + if (acceptedOrdinalSellOrders[i].ercAmount > 0) { + numOpenOrders++; + } + } + + AcceptedOrdinalSellOrder[] memory ret = new AcceptedOrdinalSellOrder[](numOpenOrders); + uint256[] memory identifiers = new uint256[](numOpenOrders); + uint256 numPushed = 0; + for (uint256 i = 0; i < nextOrdinalId; i++) { + if (acceptedOrdinalSellOrders[i].ercAmount > 0) { + ret[numPushed] = acceptedOrdinalSellOrders[i]; + identifiers[numPushed] = i; + numPushed++; + } + } + return (ret, identifiers); + } +} diff --git a/test/OnboardingPaymaster.t.sol b/test/OnboardingPaymaster.t.sol new file mode 100644 index 00000000..48295163 --- /dev/null +++ b/test/OnboardingPaymaster.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +using stdStorage for StdStorage; + +import {stdStorage, StdStorage, Test, console} from "forge-std/Test.sol"; +import {Utilities} from "./swap/Utilities.sol"; +import {OnboardingPaymaster} from "../src/paymasters/OnboardingPaymaster.sol"; +// import {} +import "lib/gsn/packages/contracts/src/BasePaymaster.sol"; + +contract OnboardingPaymasterTest is OnboardingPaymaster, Test { + Utilities internal utils; + address payable[] internal users; + address internal alice; + address internal bob; + + constructor() OnboardingPaymaster(address(0x00), 0) {} + + function setUp() public { + utils = new Utilities(); + users = utils.createUsers(5); + + alice = users[0]; + vm.label(alice, "Alice"); + bob = users[1]; + vm.label(bob, "Bob"); + } + + function testDecodeSelector() public { + bytes memory rawBytes = hex"1234567890"; + this.getSelector(rawBytes); + } +} diff --git a/test/OracleTokenPaymaster.t.sol b/test/OracleTokenPaymaster.t.sol new file mode 100644 index 00000000..4bf42a77 --- /dev/null +++ b/test/OracleTokenPaymaster.t.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; +// +// using stdStorage for StdStorage; +// +// import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; +// import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +// import {stdStorage, StdStorage, Test, console} from "forge-std/Test.sol"; +// import {Utilities} from "./swap/Utilities.sol"; +// import {OracleTokenPaymaster} from "../src/OracleTokenPaymaster.sol"; +// // import {} +// import "lib/gsn/packages/contracts/src/BasePaymaster.sol"; +// import {Oracle} from "../src/Oracle.sol"; +// +// contract ArbitaryErc20 is ERC20, Ownable { +// constructor( +// string memory name_, +// string memory symbol_ +// ) ERC20(name_, symbol_) {} +// +// function sudoMint(address to, uint256 amount) public onlyOwner { +// _mint(to, amount); +// } +// } +// +// contract OracleTokenPaymasterTest is OracleTokenPaymaster, Test { +// Utilities internal utils; +// address payable[] internal users; +// address internal alice; +// address internal bob; +// +// ArbitaryErc20 token1; +// +// constructor() OracleTokenPaymaster(new Oracle()) {} +// +// function setUp() public { +// utils = new Utilities(); +// users = utils.createUsers(5); +// +// alice = users[0]; +// vm.label(alice, "Alice"); +// bob = users[1]; +// vm.label(bob, "Bob"); +// +// token1 = new ArbitaryErc20("Some token", "TKN"); +// } +// +// function testQ() public { +// token1.sudoMint(alice, 100); +// vm.startPrank(alice); +// token1.approve(address(this), 100); +// vm.stopPrank(); +// +// IForwarder.ForwardRequest memory forwardRequest = IForwarder.ForwardRequest({ +// from: address(0x00), +// to: alice, +// value: 0, +// gas: 0, +// nonce: 0, +// data: hex"", +// validUntilTime: 0 +// }); +// +// GsnTypes.RelayData memory relayData = GsnTypes.RelayData({ +// maxFeePerGas: 0, +// maxPriorityFeePerGas: 0, +// transactionCalldataGasUsed: 0, +// relayWorker: address(0x00), +// paymaster: address(0x00), +// forwarder: address(0x00), +// paymasterData: abi.encode(token1), +// clientId: 0 +// }); +// +// GsnTypes.RelayRequest memory relayRequest = GsnTypes.RelayRequest({ +// request: forwardRequest, +// relayData: relayData +// }); +// +// bytes memory signature = hex""; +// bytes memory approvalData = hex""; +// uint maxPossibleGas = 10000; +// // this.__preRelayedCall( +// // relayRequest, +// // signature, +// // approvalData, +// // maxPossibleGas +// // ); +// +// // address tokenAddress = abi.decode(abi.encode(address(0x4Cb8b11EfF23E56A4546787b418102eD4180B2e8)), (address)); +// // assertEq(hex"00", abi.encode(address(0x4Cb8b11EfF23E56A4546787b418102eD4180B2e8))); +// address tokenAddress = abi.decode(hex"0000000000000000000000004cb8b11eff23e56a4546787b418102ed4180b2e8", (address)); +// +// // IERC20 a = this._getToken(hex"4Cb8b11EfF23E56A4546787b418102eD4180B2e8"); +// // IERC20 b = IERC20(address(0x00)); +// // +// // assertEq(address(a), address(b)); +// } +// // function testW() public { +// // bytes memory b = abi.encode(token1); +// // address a = abi.decode(b, (address)); +// // } +// } diff --git a/test/swap/Btc_Marketplace.t.sol b/test/swap/Btc_Marketplace.t.sol index 09763223..1faa5f66 100644 --- a/test/swap/Btc_Marketplace.t.sol +++ b/test/swap/Btc_Marketplace.t.sol @@ -26,9 +26,8 @@ contract MarketPlaceTest is BtcMarketPlace, Test { address internal bob; ArbitaryErc20 token1; - TestLightRelay testLightRelay; - constructor() BtcMarketPlace(testLightRelay) {} + constructor() BtcMarketPlace(testLightRelay, address(0x00)) {} function setUp() public { utils = new Utilities(); @@ -43,7 +42,6 @@ contract MarketPlaceTest is BtcMarketPlace, Test { testLightRelay = new TestLightRelay(); super.setRelay(testLightRelay); - testLightRelay.setDifficultyFromHeaders(dummyProof().bitcoinHeaders); } function dummyTransaction() public view returns (BitcoinTx.Info memory) { diff --git a/test/swap/Marketplace.t.sol b/test/swap/Marketplace.t.sol index 1056a0e3..be8b61cc 100644 --- a/test/swap/Marketplace.t.sol +++ b/test/swap/Marketplace.t.sol @@ -26,7 +26,7 @@ contract MarketPlaceTest is MarketPlace, Test { ArbitaryErc20 token1; ArbitaryErc20 token2; - constructor() MarketPlace() {} + constructor() MarketPlace(address(0x00)) {} function setUp() public { utils = new Utilities(); diff --git a/test/swap/Ord_Marketplace.t.sol b/test/swap/Ord_Marketplace.t.sol new file mode 100644 index 00000000..83af2c71 --- /dev/null +++ b/test/swap/Ord_Marketplace.t.sol @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +using stdStorage for StdStorage; + +import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {stdStorage, StdStorage, Test, console} from "forge-std/Test.sol"; +import {BtcMarketPlace} from "../../src/swap/Btc_Marketplace.sol"; +import {Utilities} from "./Utilities.sol"; +import {BitcoinTx} from "../../src/bridge/BitcoinTx.sol"; +import {TestLightRelay} from "../../src/relay/TestLightRelay.sol"; +import "../../src/swap/Ord_Marketplace.sol"; + +contract ArbitaryErc20 is ERC20, Ownable { + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} + + function sudoMint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +} + +contract OrdMarketPlaceTest is OrdMarketplace, Test { + Utilities internal utils; + address payable[] internal users; + address internal alice; + address internal bob; + + ArbitaryErc20 token1; + + struct Ordinal { + BitcoinTx.Info info; + BitcoinTx.Proof proof; + BitcoinAddress requester; + BitcoinTx.UTXO utxo; + OrdinalId id; + } + + Ordinal[3] ordinalsInfo; + + constructor() OrdMarketplace(testLightRelay) { + // data from testnet + // https://btc-testnet.gobob.xyz/tx/c872fb11bbca1241aced71c692e7d0b0cf46aadb390ce66ddfcf5fbd8e5bc26f + BitcoinTx.Info memory info = BitcoinTx.Info({ + version: hex"01000000", + inputVector: hex"0176f251d17d821b938e39b508cd3e02233d71d9b9bfe387a42a050023d3788edb0100000000ffffffff", + outputVector: hex"02a08601000000000022002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96ba2c0e0000000000160014e257eccafbc07c381642ce6e7e55120fb077fbed", + locktime: hex"00000000" + }); + + BitcoinTx.Proof memory proof = BitcoinTx.Proof({ + merkleProof: hex"c2780870a9d6f7936aaf15bb0072fa8de81036562ee557ecf8e23cd59fd80e8730515f6e07efb958e2c84c33e770bf668e3dc1470437b528814177ac38caee720df1e32074eb7735a5b5b117e575d4f8b9630156b63f7fc4dd205e5ce01741b7", + txIndexInBlock: 4, + bitcoinHeaders: abi.encodePacked( + hex"00a00020a672b6254445e7b2dd6e5433f52ea9596e6ce51776fa6ea66d0200000000000013d7683b2bfc7d7cde91c6792f62e6b9453ca2f1e72cdbf106ecabf767dd2ac5bcf98f628886021ac954f84d" + ) + }); + + BitcoinTx.UTXO memory utxo; + utxo.txHash = hex"db8e78d32300052aa487e3bfb9d9713d23023ecd08b5398e931b827dd151f276"; + utxo.txOutputIndex = 1; + utxo.txOutputValue = 0; + + // https://btc-testnet.gobob.xyz/tx/c872fb11bbca1241aced71c692e7d0b0cf46aadb390ce66ddfcf5fbd8e5bc26f + BitcoinAddress memory requester = + BitcoinAddress({scriptPubKey: hex"0014e257eccafbc07c381642ce6e7e55120fb077fbed"}); + OrdinalId memory id; + + ordinalsInfo[0] = Ordinal({info: info, proof: proof, requester: requester, utxo: utxo, id: id}); + + // data from mainnet + // get data from sdk txid: 67b7dda5dbc6b6dff1a5487e0eb01db8a22f5cf9ed1c2427cdb1f4986e14e79a + BitcoinTx.Info memory info2 = BitcoinTx.Info({ + version: hex"01000000", + inputVector: hex"063941cf4ed4dad655dcbbf363347f2ddd3eb8851991c9f4f635cfe2a26ef2498f0d000000fc00473044022035a1616b0c034a9a17aaa409a60049b4da34148ebc84b97750b20b28a67751230220580a8bac27e7e31675adcbcc27937389d1f35c976d41f9dde0e62de4c94e38260147304402200cb5d3dbc523da3a99ca4da0fc8ba35d3266939a6c6eb6e6ba70979fc9c1e93302201e91fcc95928da5e01ccc5127000a792d407987503ef59e55d07b7bdb720eefb014c69522103cbc6a30564adc716a52fc28a9ead7b06611765f6f9ecd90d19567033ed9b01b421034c939adc400e67354b4df6afcbedea2a3dc5a4c4805631426363f2fcffd709bd210270fec2b3df4961de8a5e10febb3319922b288efa1300cf048752f3a413a44e1553aefffffffff81c0fe333bc864e0c9d1b32a1dcc14352694dabb015fb6c35f9fc2f0e32ca9d00000000fdfd00004830450221009cf1afe74a98a37798b7324fd2871410b2e269c14aeb05968ca8ce1f23ebe1b4022024164457fcbac57c978dba1bc3ae53378f26aa5380d1bec975da4f85ff9c16ea01473044022042f5950266ad1be284c7ec10d7ca4e7b6cdfbec0c009c5f73f2bdbd24e85eb2902203ac84a337b6e4448a6f7e5d6720e513deefa52a3bb11ce58255756fc9ab48dfe014c695221037fe3f1cffadb5a78862ec191411a824dbc6fbc162db232cc54c845d08527a8922102e983bee0d7339993c6c64ec038a3a576fe3f71b90471a35b22f566fbdefe00e92103bb54d778d8a51d87ddbadb81930c24c316be852cc8b8b3ada9ff50c70e19c9b553aeffffffffab937ce4cf7db7259523eb70e3a2534fae8c899bf101c96eca826caa8dbb44650000000000ffffffff405eaef05128f2d4b1af6fbf7f8584b6d9937b67f18e0f73c4bc1cdf2d6280ec00000000fdfd0000483045022100aba3ac85c6f81fb692cb1227b0a525514e8d6eb46f89a4f407e547dc70b89a5b02207cec256fedfa4407a96eefdb413f79618b3d199677e2b1031a5d66008e7346b701473044022072dc0e1f6208fde1d9a94d14ac945eec6b018af9857ba2f8c5e3df9ae694070e02207328cbeae5be5680152d6b7ab1c706dd3f3098977a94cc12458a44be7b804b58014c69522103a780028aebbf4948a667fc13c7d65926b584aa89d6dcf7f5ea2781f536c99b612103f336f404719f79111f4b06589de871fc9cb7da49a03d02da36dbeb046554c4752102200acc243b2f9d8b84369a28732236c2df0709d59b1199a0a451d332b4fc93c453aeffffffff977ca6132acad11a89ceae92ea15a540ae5366f19f871a61fb6ae6b4daa5b52f00000000fdfd00004730440220352b828c387a78968cb945cca1c7001397dbb5aa723ca817159e5237ec1d69e902201059d50234972874a2aff4473baf53657e8779fb360c83770ab6305b55836720014830450221008a71067a4a4e8acca22ac59bf91d14c4d25e8693a50fbf79ff7cc392e75d2bd5022079b7a6e3f663cb443cf3e2c512176ab36b96b5ee0c03ffbe05f4404f7ed2e338014c69522102fca18dc12a5f3d1dae0a3e76d77f5f79b89d76cbb24e861c7d982be60dd4bf5d210274627fee4ab8e5953b183ddc7edbd45488e4d6faf9df9c640e05035c4af9ec5c210291ed80108c6fb853de2e4f993c7b63a8a5935ee03ac90fed8cdd1b8725c5a7b253aefffffffff00c97eb214c453a7f51b55182d448cd410dc937dbfd967135548a8a2a1f7ade00000000fdfd000047304402200645636f91c792d54346a589d021fbfb7af8803f3d8feaff2f4cd1f2e703f6dc02205eb48fa9428b6e7d1d03bc1788025ff1da58a686903e46386929ce8f810064af01483045022100b32407c41ce04d92976ebfb55018a3cf6b9bf00315c21b29c237f18e0e4f4eec022063de776c5cbdf4d3322a5229bfbea21ec12bd5470c5423cd612c4ca2f823efc4014c69522103f570642ab999a8ccaccf6d275aabb24db32907e1a37b62ab6271865c5a8194ac2103cf7901f7a585cf32aa1d9024807639540062afba94fd466f7b50417e22376d6b2102c5fa500865c57f92204a28326d126fa8eba13a0b43f860ba1eb0d4b44ce5c47c53aeffffffff", + outputVector: hex"4a6a170300000000002251207b973e017d99a513455d212caba6948c931476b70e54c58cc74448284a29bb31d06006000000000022512006119466965cd6b2a508ff44f2d8f0ec0939d13db065569f4852a54862fe0074c0cf6a000000000022512052204592e5116c27410ee0bbe1020464bdf2efe2f665b2b8c7710feccc3bc6555363220000000000225120ff4d1a953be8418ab8261bdc00a135783e7fb19d988b8e1b8239e419c80dbe2acef125000000000022512056d50486caf8eec321edf8a6c32f554cd864e90a791ea2b38919092982defc54296c0b0000000000160014ab15fbee29d9b23ac151d6ca9c0b11f234bcc0eb7d450200000000001976a914281106328aab1c5869982e05a2b6f6682982d21c88aca5d7b00000000000160014b50d63a757ca40afaf4fc2cca237e70eeaf6764209ff09000000000022512063ec8ef6c645016843d3a42618efaed45d4c9e17dc0a5cd4125951bb3117439c8c14060000000000225120f8ef2f6da914c706f60dab5c8d2439ba6c9b4ae8b171144dadc6a535910ad6eeb0c83b00000000002251209f9d8635c3737d9001a7aadf950dbb2337fe031dc368d15a059a20ad355bfac0bb6f3400000000002251201cf34ca4332a54937d3ca1604ca9b9af510e071171220fe29e96ff12f8686d83305fd400000000002251204564aee4d3aaa8d50d160bb6aa25a4a22baacf4153de378e56ae9aad88379f4415cbb0000000000017a914a98a04f77cb54c448aedcee5441a5b832b8ddb9587dd500200000000001600149db3a5ebc853369d359786376d31ae506d23c9d92e0a0500000000001600140a662f348af0985cc6dafa3aaeb70dda55002921c39e130000000000160014f06b21f8b5ea8ea86fda8a386dd91b8570fe8ff0f0010e0000000000225120a86ea4c1ad481a7aa672e91f3908c2a652a3c141cf2d831a79d99da6481a2bc000d606000000000017a914f4271a76675cdace36766295a78d4a55bdfdff8d87f00a4b000000000017a914acc75749ef4cacbbc35fb3a845f75844b29bc35f874941170000000000225120db9c5cc908474e24341816cf49806418c7cae6818b5f89c3d983745ef0ce9bf966412d000000000022512048e3c67b37187558a7779b99a3d5a054848f00ac57e9260e0fecf544f6d597ad8d57fd0500000000225120699e736b83bddd76c3395a8924371894dc739eb3fec5772b858f1675e8c33a12b97e15000000000022512061162f8814587e431f7999c10416221ff325ad2c218641b8044753dff10bd4c5041499000000000017a914e9ca361c83c46ada9a93c9e6a1b85c9ca66a662f8770862c0000000000225120d45f4cc697b09edb740965749aac06ad9b6e2d3a9d79886e6dfe6d7d57dcec06dc5e060000000000225120291493dc11642f5e84b220f0d80d07a6bf5c658efbaa75060dcccd3cc30d1c59e64b6b00000000002251209ad90b4b044e3a8fcedac0270df87494b742e437b2d39edca74ae62d7d13b7fc5b5e260000000000225120ee9be94d7c01a36a9b01bd4f1a1b7a5bd1d2816424ed68dba417ec72e7fb0f6250e52400000000001600140a7ea421458a2a0577d42990cf03a68c212e49c7042118000000000022512006fb1c6f45779f899cb5e88b4e032ff53f5c4942cbb01fb554a9cb5491847000a086010000000000225120adc382321d2e370db287205aad71d2b97a4533d5e13810e40bf976ad88f0d49d361d1400000000002251208a0ded07c10c1e9a245f2da655aba399f7bb224058907370282f7f7e024cce2fe0d14d000000000022512005921968d9c823ba9298f08a8d42d52d804bdff9375836f1aa01322f7a56902ff0010e0000000000225120a1461712396c803712a7fa2eecdf08dfe99e3f03cc38625f7fe03cb9372cd7f92c5b090000000000225120c7355433fa5e420b84d9c26386eb211e573f69955c93e418983523c422894736fdf23700000000002251209c8a5a263f3a7250390ce8918e14296d2ff53dfaeec51b40521e0a10036d205d40420f00000000001976a9141d08a974de30edcfcad5091b73a9dc944e85463e88ac6be70c0000000000225120326fafdb5f6e3f7d247dda63d82374f8c6dc6b61dcf8586b2fd911ef50ed71cf3bc808010000000022512087ea4c7415226da08724b284a5a9d07dba8a53751367dcf807ef2f0eb6ff1ffaa28a0300000000002251205c119514c9bba2920991ba3b21e3cd5a106838878319ad03c136c6af80facebe10b93b0000000000225120fd3a11d164d8dbaf2730177b35c84a83048dab306fbc5f0a6e0b2be0ac82f58358f90c00000000002251203164858dbf43d5b04e5fabaf5f25ddfb8382debf0e31af5acc0e1f0ddbda5d87816f0900000000002251208a8c9fe5a32e5be49f65115c0c8c087e1418014f4af60e097cd589677e90e36495cb3700000000001976a9149a601f879efd7e3e4c75c3ead807983ce78bd70288ac30441d000000000016001406f2512a90ead3f819d1a2cec6227e5974b837802010340000000000225120e1ca58c090c55fb4aebc10e40b514d0c041f8e295c645fa142f45c3499678f5ae4a60300000000002251204bb248e711a81edffa5088f170e1e6c4fc53a5d4118ab51b974efb264612c4db442d210000000000160014ac6382f7fb35f24ce8cdc841b1f730fd503c23e674a13f00000000002251205437ddffd90d3273e62155b9ce1bc45d294ee303daacd43c9f88befc274388a1cd9710000000000022512079cbb077e83f361ecb66777218b1e8ca4a81385c3c3b98453bd48c4d929b8d931e472a0000000000225120063d907d79279ee4ffae6a752f55600b0bb5782ece8c36a2bc71cb944d527fa260920100000000001976a9146a82de0217639b46e6a0a58ae379d5b9a227642188acf8682200000000001600143f76c9c3302be4da30e722f400a0784293b97901809698000000000022512083ad1fab826ebf0fdfd86d1cf91cf4824c1bbca2d670f21f32f374acbe6dbb934e8b08000000000022512037447bb2346dfdc0c2dd685cb9a783fa8c8a91d314780bf82b2e24ce5aceec8f96324c0000000000225120535205b265267c973529bf2009f5b98473fd8844a6e8d64ca2653ed399b9df34346e0800000000002251208e0c8a9fd4f3c72e970e0878da72cdf22216cb6cd58c8f2b4606deb27c1409833fc51d000000000022512047db5690f90eac2d8cc0d1addbee64b8b3bb45217896bf39533214de05f5bc3b90530300000000001600147b2c89ebb25b459d55d35989a05b3ef06ab57607049610000000000017a914e3b0f22fbaf6488ae811430d3374c6304bec68d9872c014b0000000000225120357a337f384949b590a1f3ab0cad3b84bae7cddad5fd38ec19384c83e2df20e4619510000000000016001430890e35976da7c0b40e0da55c302c24c3eba6eef0010e0000000000225120e435c30c6be6efc8ee7f2ab0879d3e2cba61308cafb642a5eaf46e9ff44085d670332500000000001600140a7ea421458a2a0577d42990cf03a68c212e49c7f0900b0000000000160014aefed35edcb73eeedb21724fe7c6cdf9a72f8c06d0fb0100000000001976a9146ce6f7ad56e66fcdbe21aeadb6307c1434d0215288ac44840f00000000001600140919eef26ba6449ffa215a405ab939370d5c872e191f04000000000017a914d270542327c4ce4fa1ac0852f6d55dd57b289e4a87bfb869000000000017a914f53a4273af8be7208d43e454aeaf203075491a9e87b0a0f40500000000160014438b8ffc1e62d3b967fa88b2ee1f985c1b0c64a570862c0000000000225120841488b47001df49d54cf5c397eda423a918544db7ac7c18a2dd79a9d9d70c08c2e6080000000000160014f29c0f34a4a3715907da86c1937d10dce5efa6b10bf5140300000000220020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + locktime: hex"00000000" + }); + + BitcoinTx.Proof memory proof2 = BitcoinTx.Proof({ + merkleProof: hex"b3ed2cbdb45beafc848cba83cdfec403de217702f93d2ef760ef947f4c25364a889f2744d13099fb75294ba3accea7f2f57b080c5c330c7cc4ef5e474f2f3ae43e30f9d1fa0b701a487100ee77eab74c9c22a6fc54e7e65a58d47606aa2c38cb359d4ab035a25ec029181ac67866d74b8f99ef0dba9325a907f21d14eae9de529523c85f4b49d1b69e351f70acfbc03a4f54108accaa85d52bfca065bbc1c2feb08c9dc1a2f87cdba5ad1017c70b4356ab86040538a60b8166afd59b185fa577a26a81b037de042f501d5a42a6efad1f2bf0d82208ebd3324b3923c9c358d7da5644be7757e3d5318ee584b01c31ae1f2f9c8edab8a87c0ebb6e3d7a4d7731138ef5f9f8d9de6ab54954f9f8eca8ad8b03a3d24b82ada0104944dfaf1752ac43abc2887f5fd3d6e03ce7fd819f6260d972adcbbdc35a41bffbc57c6647f1a054c220414f359dc95a81a3644d22f7b62b4f253b5b97a300f47a40a2a681cee1a3a1db92ab7b6c098bdc88b276695f5b52eff831c9678a6b20576814333943b0db", + txIndexInBlock: 25, + bitcoinHeaders: abi.encodePacked( + hex"00200020b70169ce36e45282d9e32c6b3cf815c4580872c94d7801000000000000000000a9bb880a70ef5e045fa684064c5229fb1c540a799e2bdb71efde06304a26de4dc2c77a65952e0417d2418bf4" + ) + }); + + BitcoinTx.UTXO memory utxo2; + utxo2.txHash = hex"de7a1f2a8a8a54357196fddb37c90d41cd48d48251b5517f3a454c21eb970cf0"; + utxo2.txOutputIndex = 0; + utxo2.txOutputValue = 0; + + // https://btc-mainnet.gobob.xyz/tx/67b7dda5dbc6b6dff1a5487e0eb01db8a22f5cf9ed1c2427cdb1f4986e14e79a + BitcoinAddress memory requester2 = + BitcoinAddress({scriptPubKey: hex"5120d45f4cc697b09edb740965749aac06ad9b6e2d3a9d79886e6dfe6d7d57dcec06"}); + OrdinalId memory id2; + + ordinalsInfo[1] = Ordinal({info: info2, proof: proof2, requester: requester2, utxo: utxo2, id: id2}); + + // data from testnet + // get data from sdk txid: 591235b1a474ea29e29e2b3aaee45055b43e38cdf38c3700df65509f60ee2d8e + BitcoinTx.Info memory info3 = BitcoinTx.Info({ + version: hex"02000000", + inputVector: hex"022c73deced32f831ee0c7f9cda848b11ed1f284e6b3251814ca3b6c028d80216e0000000000ffffffffb07f925d2e91c4ff4bf802f38e60c390a8498f650e06ccc8eac32e6913aa3b450100000000ffffffff", + outputVector: hex"0222020000000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac178e07000000000022512025f4ee2e73ace3ed0e5ec4472541db5755b52cb8823e8ce14b6052dedfe3b33d", + locktime: hex"00000000" + }); + + BitcoinTx.Proof memory proof3 = BitcoinTx.Proof({ + merkleProof: hex"b1b47c8e4fcf75b9820ef433d7b5a6fa65d559af35173c600b44e736deb5ffd23a1cebfd570521a1153ef16e9fc4eeb8f80d699fdf113e96e7b5c9dfdaf0db8e166f972b6eff1e8d248d1bc3732dfb4e2aa7a78ca0e6d99c01644ce521c02b2f90c4848b9e60e8d44c764c58affefdc9dd40ce2f4f537b923551676a008f8adea365f60d22ae64f00aa2d981e640ee2f4f0781e4d347ea1d154bbf3c829ab2d02e69bdb26bef5f06781b439b6e65ac701cf458018bfb23304817efb66fabf3ba10317933e969f8bd6e6e3aa48f75b0d7c53adbe80dc9dca82d0e66aef5c592d66e6bdbd0630efb413b54c208c9465cc747701261978de3c8f3c6c33923242c67", + txIndexInBlock: 72, + bitcoinHeaders: abi.encodePacked( + hex"0000002019ecad2d640319bad3cbe99dd2819fdba90023ccd6a479d31400000000000000f308f7de96bab7e11e64fdb06000e57806b7791b923cf0e8382547d3c609f2bf65438165ffff001d84cd32400000e0201a1e9727a2da2e112c761a0d3aa639c52e9b43c1e9d80bb0242632240000000056f577d01fbd2550679ced5da24291ec48bbb96a93f790349ac2f54fa075f90c64448165efdf2819529ec2d8" + ) + }); + + // https://mempool.space/testnet/tx/6e21808d026c3bca141825b3e684f2d11eb148a8cdf9c7e01e832fd3cede732c + // ordinal inscription: https://ordinals-testnet.gamma.io/inscription/6e21808d026c3bca141825b3e684f2d11eb148a8cdf9c7e01e832fd3cede732ci0 + // ordinal tx: https://ordinals-testnet.gamma.io/tx/591235b1a474ea29e29e2b3aaee45055b43e38cdf38c3700df65509f60ee2d8e + BitcoinTx.UTXO memory utxo3; + utxo3.txHash = hex"6e21808d026c3bca141825b3e684f2d11eb148a8cdf9c7e01e832fd3cede732c"; + utxo3.txOutputIndex = 0; + utxo3.txOutputValue = 546; + + // https://btc-testnet.gobob.xyz/tx/591235b1a474ea29e29e2b3aaee45055b43e38cdf38c3700df65509f60ee2d8e + BitcoinAddress memory requester3 = + BitcoinAddress({scriptPubKey: hex"76a914344a0f48ca150ec2b903817660b9b68b13a6702688ac"}); + OrdinalId memory id3; + + ordinalsInfo[2] = Ordinal({info: info3, proof: proof3, requester: requester3, utxo: utxo3, id: id3}); + } + + function setUp() public { + utils = new Utilities(); + users = utils.createUsers(5); + + alice = users[0]; + vm.label(alice, "Alice"); + bob = users[1]; + vm.label(bob, "Bob"); + + token1 = new ArbitaryErc20("Some token", "TKN"); + + testLightRelay = new TestLightRelay(); + super.setRelay(testLightRelay); + } + + function test_ordinalSellOrderFullFlow() public { + uint256 nextOrdinalId; + + for (uint256 i = 0; i < ordinalsInfo.length; i++) { + token1.sudoMint(bob, 100); + + uint256 expectedPlaceId = nextOrdinalId++; + + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + vm.expectEmit(); + emit placeOrdinalSellOrderEvent(expectedPlaceId, ordinalsInfo[i].id, address(token1), 100); + this.placeOrdinalSellOrder(ordinalsInfo[i].id, ordinalsInfo[i].utxo, address(token1), 100); + + uint256 expectedAcceptId = nextOrdinalId++; + + // acceptOrdinalSellOrder by bob + vm.startPrank(bob); + token1.approve(address(this), 100); + + vm.expectEmit(); + emit acceptOrdinalSellOrderEvent( + expectedPlaceId, expectedAcceptId, ordinalsInfo[i].requester, address(token1), 100 + ); + uint256 acceptId = this.acceptOrdinalSellOrder(expectedPlaceId, ordinalsInfo[i].requester); + assertEq(expectedAcceptId, expectedAcceptId); + + // proofOrdinalSellOrder + vm.startPrank(alice); + vm.expectEmit(); + emit proofOrdinalSellOrderEvent(expectedAcceptId); + this.proofOrdinalSellOrder(expectedAcceptId, ordinalsInfo[i].info, ordinalsInfo[i].proof); + vm.stopPrank(); + } + } + + function test_placeOrdinalSellOrderShouldRevert() public { + token1.sudoMint(bob, 200); + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + vm.expectRevert("Invalid buying token"); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[0].utxo, address(0x0), 100); + + vm.expectRevert("Buying amount should be greater than 0"); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[0].utxo, address(token1), 0); + + vm.stopPrank(); + } + + function setUpForAcceptOrdinalSellOrder() public { + token1.sudoMint(bob, 200); + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[0].utxo, address(token1), 100); + } + + function test_acceptOrdinalSellOrderShouldRevert() public { + setUpForAcceptOrdinalSellOrder(); + + vm.startPrank(bob); + + // allow insufficient tokens + token1.approve(address(this), 50); + vm.expectRevert("ERC20: insufficient allowance"); + this.acceptOrdinalSellOrder(0, ordinalsInfo[0].requester); + + // call with wrong id + token1.approve(address(this), 100); + vm.expectRevert("Address: call to non-contract"); + this.acceptOrdinalSellOrder(1, ordinalsInfo[0].requester); + vm.stopPrank(); + } + + function test_acceptOrdinalSellOrderWhenOrderAlreadyAccepted() public { + setUpForProofOrdinalSellOrder(); + + // acceptOrdinalSellOrder by bob + vm.startPrank(bob); + token1.approve(address(this), 100); + vm.expectRevert("Order Already Accepted"); + this.acceptOrdinalSellOrder(0, ordinalsInfo[0].requester); + } + + function setUpForProofOrdinalSellOrder() public { + token1.sudoMint(bob, 200); + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[0].utxo, address(token1), 100); + + // acceptOrdinalSellOrder by bob + vm.startPrank(bob); + token1.approve(address(this), 100); + this.acceptOrdinalSellOrder(0, ordinalsInfo[0].requester); + } + + function test_proofOrdinalSellOrderShouldRevert() public { + setUpForProofOrdinalSellOrder(); + + // when sender is not the requester + vm.startPrank(bob); + vm.expectRevert("Sender not the requester"); + this.proofOrdinalSellOrder(1, ordinalsInfo[0].info, ordinalsInfo[0].proof); + + // with invalid id + vm.startPrank(alice); + vm.expectRevert("Sender not the requester"); + this.proofOrdinalSellOrder(2, ordinalsInfo[0].info, ordinalsInfo[0].proof); + vm.stopPrank(); + } + + function test_acceptProofOrdinalSellOrderWithInvalidMerkelProof() public { + setUpForProofOrdinalSellOrder(); + // with invalid proof + vm.startPrank(alice); + vm.expectRevert("Tx merkle proof is not valid for provided header and tx hash"); + this.proofOrdinalSellOrder(1, ordinalsInfo[0].info, ordinalsInfo[1].proof); + vm.stopPrank(); + } + + function test_acceptProofOrdinalSellOrderWithUtxoSpentOnInCorrectAddress() public { + setUpForAcceptOrdinalSellOrder(); + + // acceptOrdinalSellOrder by bob + vm.startPrank(bob); + token1.approve(address(this), 100); + this.acceptOrdinalSellOrder(0, ordinalsInfo[1].requester); + + vm.startPrank(alice); + vm.expectRevert("No output found for scriptPubKey"); + this.proofOrdinalSellOrder(1, ordinalsInfo[0].info, ordinalsInfo[0].proof); + vm.stopPrank(); + } + + function test_acceptProofOrdinalSellOrderWithInvalidUtxoSpent() public { + token1.sudoMint(bob, 200); + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[1].utxo, address(token1), 100); + + // acceptOrdinalSellOrder by bob + vm.startPrank(bob); + token1.approve(address(this), 100); + this.acceptOrdinalSellOrder(0, ordinalsInfo[0].requester); + + vm.startPrank(alice); + vm.expectRevert("Transaction does not spend the required utxo"); + this.proofOrdinalSellOrder(1, ordinalsInfo[0].info, ordinalsInfo[0].proof); + } + + function test_withdrawOrdinalSellOrder() public { + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[1].utxo, address(token1), 100); + this.withdrawOrdinalSellOrder(0); + + (OrdinalSellOrder[] memory _ordinalOrders, uint256[] memory ids) = this.getOpenOrdinalSellOrders(); + // there should be no order ids + assertEq(ids.length, 0); + } + + function test_withdrawOrdinalSellOrderShouldRevert() public { + // placeOrdinalSellOrder by alice + vm.startPrank(alice); + this.placeOrdinalSellOrder(ordinalsInfo[0].id, ordinalsInfo[1].utxo, address(token1), 100); + + vm.startPrank(bob); + vm.expectRevert("Sender not the requester"); + this.withdrawOrdinalSellOrder(0); + } + + function test_cancelAcceptedOrdinalSellOrder() public { + setUpForProofOrdinalSellOrder(); + vm.warp(block.timestamp + REQUEST_EXPIRATION_SECONDS + 1); + vm.startPrank(bob); + this.cancelAcceptedOrdinalSellOrder(1); + (AcceptedOrdinalSellOrder[] memory _ordinalAcceptedOrders, uint256[] memory ids) = + this.getOpenAcceptedOrdinalSellOrders(); + // there should be no order ids + assertEq(ids.length, 0); + } + + function test_cancelAcceptedOrdinalSellOrderShouldRevert() public { + setUpForProofOrdinalSellOrder(); + + vm.startPrank(bob); + vm.expectRevert("Request still valid"); + this.cancelAcceptedOrdinalSellOrder(1); + + vm.warp(block.timestamp + REQUEST_EXPIRATION_SECONDS + 1); + + vm.startPrank(alice); + vm.expectRevert("Sender not the acceptor"); + this.cancelAcceptedOrdinalSellOrder(1); + } +}