├── .cargo └── config.toml ├── .clinerules ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── README.md ├── build.rs ├── crates ├── alkanes-build │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── alkanes-macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-runtime │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── auth.rs │ │ ├── compat.rs │ │ ├── imports.rs │ │ ├── lib.rs │ │ ├── message.rs │ │ ├── meta.rs │ │ ├── runtime.rs │ │ ├── stdio.rs │ │ ├── storage.rs │ │ └── token.rs ├── alkanes-std-auth-token │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-factory-support │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-genesis-alkane │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── chain.rs │ │ └── lib.rs ├── alkanes-std-genesis-protorune │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-merkle-distributor │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-orbital │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-owned-token │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-proxy │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-test │ ├── .cargo │ │ └── config.toml │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-std-upgradeable │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── alkanes-support │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ │ ├── alkanes.proto │ │ └── protorune.proto │ └── src │ │ ├── cellpack.rs │ │ ├── constants.rs │ │ ├── context.rs │ │ ├── envelope.rs │ │ ├── gz.rs │ │ ├── id.rs │ │ ├── lib.rs │ │ ├── parcel.rs │ │ ├── proto │ │ ├── alkanes.rs │ │ └── mod.rs │ │ ├── response.rs │ │ ├── storage.rs │ │ ├── trace │ │ ├── block.rs │ │ ├── mod.rs │ │ └── types.rs │ │ ├── utils.rs │ │ └── witness.rs ├── ordinals │ ├── Cargo.toml │ └── src │ │ ├── artifact.rs │ │ ├── cenotaph.rs │ │ ├── charm.rs │ │ ├── decimal_sat.rs │ │ ├── degree.rs │ │ ├── edict.rs │ │ ├── epoch.rs │ │ ├── etching.rs │ │ ├── flaw.rs │ │ ├── height.rs │ │ ├── lib.rs │ │ ├── pile.rs │ │ ├── rarity.rs │ │ ├── rune.rs │ │ ├── rune_id.rs │ │ ├── runestone.rs │ │ ├── runestone │ │ ├── flag.rs │ │ ├── message.rs │ │ └── tag.rs │ │ ├── sat.rs │ │ ├── sat_point.rs │ │ ├── spaced_rune.rs │ │ ├── terms.rs │ │ └── varint.rs ├── protorune-support │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ │ └── protorune.proto │ └── src │ │ ├── balance_sheet.rs │ │ ├── byte_utils.rs │ │ ├── constants.rs │ │ ├── lib.rs │ │ ├── network.rs │ │ ├── proto │ │ ├── mod.rs │ │ └── protorune.rs │ │ ├── protostone.rs │ │ ├── rune_transfer.rs │ │ └── utils.rs └── protorune │ ├── .cargo │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── balance_sheet.rs │ ├── lib.rs │ ├── message.rs │ ├── protoburn.rs │ ├── protorune_init.rs │ ├── protostone.rs │ ├── tables.rs │ ├── test_helpers.rs │ ├── tests │ ├── index_op_return_position.rs │ ├── index_pointer_ll.rs │ ├── index_protoburns.rs │ ├── index_protomessage.rs │ ├── index_protorunes_by_address.rs │ ├── index_runes.rs │ ├── index_runes_edicts.rs │ ├── index_runes_mint.rs │ ├── mod.rs │ ├── multi_protocol.rs │ └── test_cenotaphs.rs │ └── view.rs ├── memory-bank ├── activeContext.md ├── productContext.md ├── progress.md ├── projectBrief.md ├── systemPatterns.md └── techContext.md ├── rust-toolchain.toml ├── src ├── block.rs ├── etl.rs ├── indexer.rs ├── lib.rs ├── message.rs ├── network.rs ├── precompiled │ ├── .test │ ├── alkanes_std_auth_token_build.rs │ ├── alkanes_std_genesis_alkane_bellscoin_build.rs │ ├── alkanes_std_genesis_alkane_build.rs │ ├── alkanes_std_genesis_alkane_dogecoin_build.rs │ ├── alkanes_std_genesis_alkane_fractal_build.rs │ ├── alkanes_std_genesis_alkane_luckycoin_build.rs │ ├── alkanes_std_genesis_alkane_mainnet_build.rs │ ├── alkanes_std_genesis_alkane_regtest_build.rs │ ├── alkanes_std_owned_token_build.rs │ ├── alkanes_std_proxy_build.rs │ └── mod.rs ├── proto │ ├── alkanes.rs │ ├── mod.rs │ └── protorune.rs ├── tables.rs ├── tests │ ├── abi_test.rs │ ├── address.rs │ ├── alkane.rs │ ├── arbitrary_alkane_mint.rs │ ├── auth_token.rs │ ├── crash.rs │ ├── determinism.rs │ ├── edict_then_message.rs │ ├── forge.rs │ ├── fuel.rs │ ├── genesis.rs │ ├── helpers.rs │ ├── memory_security_tests.rs │ ├── mod.rs │ ├── networks.rs │ ├── serialization.rs │ ├── special_extcall.rs │ ├── static │ │ ├── 849236.txt │ │ └── 849250.txt │ ├── trace.rs │ ├── upgradeable.rs │ ├── utils.rs │ ├── vec_input_test.rs │ └── view.rs ├── trace.rs ├── utils.rs ├── view.rs └── vm │ ├── constants.rs │ ├── exports.rs │ ├── extcall.rs │ ├── fuel.rs │ ├── host_functions.rs │ ├── instance.rs │ ├── mod.rs │ ├── runtime.rs │ ├── state.rs │ └── utils.rs └── update_version.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | 4 | [target.wasm32-unknown-unknown] 5 | runner = "wasm-bindgen-test-runner" 6 | -------------------------------------------------------------------------------- /.clinerules: -------------------------------------------------------------------------------- 1 | ## Cline's Memory Bank 2 | 3 | I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional. 4 | 5 | ## Memory Bank Structure 6 | 7 | The Memory Bank consists of required core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy 8 | 9 | ``` 10 | flowchart TD 11 | PB[projectbrief.md] --> PC[productContext.md] 12 | PB --> SP[systemPatterns.md] 13 | PB --> TC[techContext.md] 14 | 15 | PC --> AC[activeContext.md] 16 | SP --> AC 17 | TC --> AC 18 | 19 | AC --> P[progress.md] 20 | ``` 21 | 22 | ## Core Files (Required) 23 | 1. `projectbrief.md` 24 | - Created at project start if it doesn't exist 25 | - Foundation document that shapes all other files 26 | - Defines core requirements and goals 27 | - Source of truth for project scope 28 | 29 | 2. `productContext.md` 30 | - Why this project exists 31 | - Problems it solves 32 | - How it should work 33 | - User experience goals 34 | 35 | 3. `activeContext.md` 36 | - Current work focus 37 | - Recent changes 38 | - Next steps 39 | - Active decisions and considerations 40 | 41 | 4. `systemPatterns.md` 42 | - System architecture 43 | - Key technical decisions 44 | - Design patterns in use 45 | - Component relationships 46 | 47 | 5. `techContext.md` 48 | - Technologies used 49 | - Development setup 50 | - Technical constraints 51 | - Dependencies 52 | 53 | 6. `progress.md` 54 | - What works 55 | - What's left to build 56 | - Current status 57 | - Known issues 58 | 59 | ### Additional Context 60 | 61 | - Complex feature documentation 62 | - Integration specifications 63 | - API documentation 64 | - Testing strategies 65 | - Deployment procedures 66 | 67 | ## Core Workflows 68 | 69 | ### Plan Mode 70 | 71 | ``` 72 | flowchart TD 73 | Start[Start] --> ReadFiles[Read Memory Bank] 74 | ReadFiles --> CheckFiles{Files Complete?} 75 | 76 | CheckFiles -->|No| Plan[Create Plan] 77 | Plan --> Document[Document in Chat] 78 | 79 | CheckFiles -->|Yes| Verify[Verify Context] 80 | Verify --> Strategy[Develop Strategy] 81 | Strategy --> Present[Present Approach] 82 | ``` 83 | 84 | ### Act Mode 85 | 86 | ``` 87 | flowchart TD 88 | Start[Start] --> Context[Check Memory Bank] 89 | Context --> Update[Update Documentation] 90 | Update --> Rules[Update .clinerules if needed] 91 | Rules --> Execute[Execute Task] 92 | Execute --> Document[Document Changes] 93 | 94 | ``` 95 | 96 | ### Documentation Updates 97 | 98 | 99 | Memory Bank updates occur when: 100 | 101 | 1. Discovering new project patterns 102 | 2. After implementing significant changes 103 | 3. When user requests with update memory bank (MUST review ALL files) 104 | 4. When context needs clarification 105 | 106 | ``` 107 | flowchart TD 108 | Start[Update Process] 109 | 110 | subgraph Process 111 | P1[Review ALL Files] 112 | P2[Document Current State] 113 | P3[Clarify Next Steps] 114 | P4[Update .clinerules] 115 | 116 | P1 --> P2 --> P3 --> P4 117 | end 118 | 119 | Start --> Process 120 | ``` 121 | 122 | Note: When triggered by update memory bank, I MUST review every memory bank file, even if some don't require updates. Focus particularly on `activeContext.md` and `progress.md` as they track current state. 123 | 124 | ## Project Intelligence (projectJournal) 125 | 126 | The `projectJournal.md` file is my learning journal for each project. It captures important patterns, preferences, and project intelligence that help me work more effectively. As I work with you and the project, I'll discover and document key insights that aren't obvious from the code alone. 127 | 128 | ``` 129 | flowchart TD 130 | Start{Discover New Pattern} 131 | 132 | subgraph Learn [Learning Process] 133 | D1[Identify Pattern] 134 | D2[Validate with User] 135 | D3[Document in .clinerules] 136 | end 137 | 138 | subgraph Apply [Usage] 139 | A1[Read .clinerules] 140 | A2[Apply Learned Patterns] 141 | A3[Improve Future Work] 142 | end 143 | 144 | Start --> Learn 145 | Learn --> Apply 146 | ``` 147 | 148 | ### What to Capture 149 | - Critical implementation paths 150 | - User preferences and workflow 151 | - Project-specific patterns 152 | - Known challenges 153 | - Evolution of project decisions 154 | - Tool usage patterns 155 | 156 | 157 | The format is flexible - focus on capturing valuable insights that help me work more effectively with you and the project. Think of .clinerules as a living document that grows smarter as we work together. 158 | 159 | REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy. -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | test-config: 18 | - name: "WASM Tests" 19 | command: "cargo test --target wasm32-unknown-unknown --features test-utils" 20 | - name: "Unit Tests" 21 | command: "cargo test --target x86_64-unknown-linux-gnu --features test-utils" 22 | - name: "Protorunes WASM Tests" 23 | command: "cargo test -p protorune --features test-utils" 24 | - name: "Protorune Unit Tests" 25 | command: "cargo test -p protorune --target x86_64-unknown-linux-gnu" 26 | fail-fast: false # Allows all tests to run even if one fails 27 | 28 | name: ${{ matrix.test-config.name }} 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Install Rust 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | targets: wasm32-unknown-unknown 35 | - name: Install test runner 36 | run: cargo install wasm-bindgen-cli --version 0.2.100 37 | - name: ${{ matrix.test-config.name }} 38 | run: ${{ matrix.test-config.command }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | target 4 | src/tests/std/**/*.rs 5 | memory-bank/activeContext.md 6 | memory-bank/progress.md 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes" 3 | version = "0.2.3" 4 | edition = "2021" 5 | description = "ALKANES metaprotocol indexer" 6 | license = "MIT" 7 | repository = "https://github.com/kungfuflex/alkanes-rs" 8 | resolver = "2" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [workspace] 14 | members = [".", "crates/*"] 15 | 16 | 17 | [workspace.dependencies] 18 | anyhow = "1.0.90" 19 | num = "0.4.3" 20 | bitcoin = { version = "0.32.4", features = ["rand"] } 21 | metashrew-core = { git = "https://github.com/sandshrewmetaprotocols/metashrew" } 22 | metashrew-support = { git = "https://github.com/sandshrewmetaprotocols/metashrew" } 23 | ordinals = { path = "./crates/ordinals" } 24 | protorune = { path = "./crates/protorune" } 25 | protorune-support = { path = "./crates/protorune-support" } 26 | alkanes-support = { path = "./crates/alkanes-support" } 27 | alkanes-runtime = { path = "./crates/alkanes-runtime" } 28 | alkanes-macros = { path = "./crates/alkanes-macros" } 29 | alkanes-std-factory-support = { path = "./crates/alkanes-std-factory-support" } 30 | ruint = "1.12.3" 31 | wasm-bindgen = "0.2.100" 32 | byteorder = "1.5" 33 | wasm-bindgen-test = "0.3.49" 34 | wasmi = "0.37.2" 35 | serde = "1.0.210" 36 | serde_json = "1.0.128" 37 | hex = "0.4.3" 38 | protobuf = "3.7.1" 39 | wasm-bindgen-futures = "0.4.45" 40 | web-sys = { version = "0.3.72", features = ["Response", "Window"] } 41 | js-sys = "0.3.72" 42 | hex_lit = "0.1.1" 43 | once_cell = "1.20.1" 44 | 45 | [features] 46 | test-utils = [] 47 | testnet = [] 48 | dogecoin = [] 49 | luckycoin = [] 50 | bellscoin = [] 51 | fractal = [] 52 | mainnet = [] 53 | proxy = [] 54 | owned_token = [] 55 | auth_token = [] 56 | genesis_alkane = [] 57 | regtest = [] 58 | genesis_protorune = [] 59 | amm_pool = ["auth_token"] 60 | amm_factory = ["auth_token"] 61 | amm = ["amm_pool", "amm_factory"] 62 | orbital = [] 63 | cache = ["protorune/cache"] 64 | all = [] 65 | minimal = [ 66 | "refunder", 67 | "merkle_distributor", 68 | "free_mint", 69 | "upgradeable", 70 | "proxy", 71 | ] 72 | refunder = [] 73 | merkle_distributor = [] 74 | free_mint = [] 75 | upgradeable = [] 76 | debug-log = [] 77 | 78 | 79 | [dependencies] 80 | anyhow = { workspace = true } 81 | num = { workspace = true } 82 | bitcoin = { workspace = true } 83 | metashrew-core = { workspace = true } 84 | metashrew-support = { workspace = true } 85 | ordinals = { workspace = true } 86 | protorune = { workspace = true } 87 | protorune-support = { workspace = true } 88 | alkanes-support = { workspace = true } 89 | ruint = { workspace = true } 90 | wasm-bindgen = { workspace = true } 91 | byteorder = { workspace = true } 92 | wasm-bindgen-test = { workspace = true } 93 | wasmi = { workspace = true } 94 | serde = { workspace = true } 95 | serde_json = { workspace = true } 96 | hex = { workspace = true } 97 | protobuf = { workspace = true } 98 | wasm-bindgen-futures = { workspace = true } 99 | web-sys = { workspace = true } 100 | js-sys = { workspace = true } 101 | hex_lit = { workspace = true } 102 | once_cell = { workspace = true } 103 | 104 | [dev-dependencies] 105 | 106 | alkanes = { path = ".", features = [ 107 | "genesis_alkane", 108 | "genesis_protorune", 109 | "amm", 110 | "orbital", 111 | "auth_token", 112 | "minimal", 113 | ] } 114 | metashrew-core = { git = "https://github.com/sandshrewmetaprotocols/metashrew", features = ["test-utils"] } 115 | protorune = { path = "crates/protorune", features = ["test-utils"] } 116 | 117 | [build-dependencies] 118 | anyhow = "1.0.90" 119 | protobuf-codegen = "3.4.0" 120 | flate2 = "1.0.34" 121 | protoc-rust = { version = "2.28.0" } 122 | protoc-bin-vendored = "3.0.0" 123 | hex = "0.4.3" 124 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.85.1-slim-bullseye as builder 2 | 3 | # Install dependencies 4 | RUN apt-get update && apt-get install -y \ 5 | git \ 6 | pkg-config \ 7 | libssl-dev \ 8 | build-essential \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | # Install wasm32-unknown-unknown target 12 | RUN rustup target add wasm32-unknown-unknown 13 | 14 | # Clone metashrew repository at specific branch 15 | WORKDIR /usr/src 16 | RUN git clone https://github.com/sandshrewmetaprotocols/metashrew.git -b v8.5.1-rc1 17 | WORKDIR /usr/src/metashrew 18 | RUN apt-get update && apt-get install -y \ 19 | libssl-dev \ 20 | libclang-dev \ 21 | clang \ 22 | pkg-config \ 23 | ca-certificates \ 24 | && rm -rf /var/lib/apt/lists/* 25 | 26 | # Build rockshrew-mono 27 | RUN cargo build --release -p rockshrew-mono 28 | 29 | # Copy and build alkanes-rs project 30 | WORKDIR /usr/src 31 | COPY . /usr/src/alkanes-rs 32 | WORKDIR /usr/src/alkanes-rs 33 | 34 | # Build alkanes with mainnet feature 35 | RUN cargo build --release --target wasm32-unknown-unknown --features mainnet 36 | 37 | # Create a smaller runtime image 38 | FROM debian:bullseye-slim 39 | 40 | # Install runtime dependencies 41 | RUN apt-get update && apt-get install -y \ 42 | libssl-dev \ 43 | libclang-dev \ 44 | pkg-config \ 45 | ca-certificates \ 46 | && rm -rf /var/lib/apt/lists/* 47 | 48 | # Create directory for RocksDB data 49 | RUN mkdir -p /data/rocksdb 50 | 51 | # Copy built binaries from builder stage 52 | COPY --from=builder /usr/src/metashrew/target/release/rockshrew-mono /usr/local/bin/ 53 | COPY --from=builder /usr/src/alkanes-rs/target/wasm32-unknown-unknown/release/alkanes.wasm /usr/local/bin/ 54 | 55 | # Set working directory 56 | WORKDIR /data 57 | 58 | # Expose the port 59 | EXPOSE 8080 60 | 61 | # Set entrypoint 62 | ENTRYPOINT ["rockshrew-mono", \ 63 | "--db-path", "/data/rocksdb", \ 64 | "--indexer", "/usr/local/bin/alkanes.wasm", \ 65 | "--cors", "*", \ 66 | "--host", "0.0.0.0", \ 67 | "--port", "8080", \ 68 | "--start-block", "880000"] 69 | 70 | # Default command (can be overridden) 71 | CMD [] 72 | 73 | # Usage: 74 | # docker build -t alkanes-rs . 75 | # docker run -p 8080:8080 -v /path/to/local/data:/data/rocksdb \ 76 | # alkanes-rs --daemon-rpc-url http://bitcoind:8332 --auth rpcuser:rpcpassword 77 | -------------------------------------------------------------------------------- /crates/alkanes-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-build" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.4", features = ["derive"] } 8 | hex = "0.4" 9 | -------------------------------------------------------------------------------- /crates/alkanes-build/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Parser)] 6 | #[command(author, version, about, long_about = None)] 7 | struct Args { 8 | /// Path to input WASM file 9 | #[arg(short, long)] 10 | input: PathBuf, 11 | 12 | /// Path to output _build.rs file 13 | #[arg(short, long)] 14 | output: PathBuf, 15 | } 16 | 17 | fn main() -> std::io::Result<()> { 18 | let args = Args::parse(); 19 | 20 | // Read WASM file bytes 21 | let wasm_bytes = fs::read(&args.input)?; 22 | 23 | // Convert to hex string 24 | let hex_string = hex::encode(&wasm_bytes); 25 | 26 | // Generate build.rs content 27 | let build_content = format!( 28 | "use hex_lit::hex;\n#[allow(long_running_const_eval)]\npub fn get_bytes() -> Vec {{ (&hex!(\"{}\")).to_vec() }}", 29 | hex_string 30 | ); 31 | 32 | // Write output file 33 | fs::write(&args.output, build_content)?; 34 | 35 | println!( 36 | "Successfully converted {} to {}", 37 | args.input.display(), 38 | args.output.display() 39 | ); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /crates/alkanes-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-macros" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = { version = "1.0", features = ["full", "extra-traits"] } 11 | quote = "1.0" 12 | proc-macro2 = "1.0" 13 | serde_json = "1.0" 14 | ciborium = "0.2" -------------------------------------------------------------------------------- /crates/alkanes-runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-runtime" 3 | version = "0.2.3" 4 | edition = "2021" 5 | description = "ALKANES runtime library" 6 | license = "MIT" 7 | repository = "https://github.com/kungfuflex/alkanes-rs" 8 | resolver = "2" 9 | 10 | [features] 11 | test-utils = [] 12 | panic-hook = [] 13 | 14 | [lib] 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | [dependencies] 18 | alkanes-support = { workspace = true } 19 | alkanes-macros = { workspace = true } 20 | anyhow = { workspace = true } 21 | bitcoin = { workspace = true } 22 | metashrew-support = { workspace = true } 23 | wasm-bindgen = { workspace = true } 24 | wasm-bindgen-test = { workspace = true } 25 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/auth.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use crate::{ 3 | println, 4 | stdio::{stdout, Write}, 5 | }; 6 | use crate::{runtime::AlkaneResponder, storage::StoragePointer}; 7 | use alkanes_support::{ 8 | cellpack::Cellpack, 9 | constants::AUTH_TOKEN_FACTORY_ID, 10 | id::AlkaneId, 11 | parcel::{AlkaneTransfer, AlkaneTransferParcel}, 12 | }; 13 | use anyhow::{anyhow, Result}; 14 | use metashrew_support::index_pointer::KeyValuePointer; 15 | use std::sync::Arc; 16 | 17 | pub trait AuthenticatedResponder: AlkaneResponder { 18 | fn deploy_auth_token(&self, units: u128) -> Result { 19 | let cellpack = Cellpack { 20 | target: AlkaneId { 21 | block: 6, 22 | tx: AUTH_TOKEN_FACTORY_ID, 23 | }, 24 | inputs: vec![0x0, units], 25 | }; 26 | let sequence = self.sequence(); 27 | let response = self.call(&cellpack, &AlkaneTransferParcel::default(), self.fuel())?; 28 | let mut ptr = StoragePointer::from_keyword("/auth"); 29 | ptr.set(Arc::new(>>::into(AlkaneId { 30 | block: 2, 31 | tx: sequence, 32 | }))); 33 | if response.alkanes.0.len() < 1 { 34 | Err(anyhow!("auth token not returned with factory")) 35 | } else { 36 | Ok(response.alkanes.0[0]) 37 | } 38 | } 39 | fn auth_token(&self) -> Result { 40 | let pointer = StoragePointer::from_keyword("/auth").get(); 41 | Ok(pointer.as_ref().clone().try_into()?) 42 | } 43 | fn only_owner(&self) -> Result<()> { 44 | let context = self.context()?; 45 | let auth_token = self.auth_token()?; 46 | if !context 47 | .incoming_alkanes 48 | .0 49 | .iter() 50 | .any(|i| i.id == auth_token) 51 | { 52 | return Err(anyhow!("Auth token is not in incoming alkanes")); 53 | } 54 | let cellpack = Cellpack { 55 | target: auth_token, 56 | inputs: vec![0x1], 57 | }; 58 | let response = self.call( 59 | &cellpack, 60 | &AlkaneTransferParcel(vec![AlkaneTransfer { 61 | id: cellpack.target.clone(), 62 | value: 1, 63 | }]), 64 | self.fuel(), 65 | )?; 66 | if response.data == vec![0x01] { 67 | Ok(()) 68 | } else { 69 | Err(anyhow!("only_owner: returned error")) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/compat.rs: -------------------------------------------------------------------------------- 1 | use crate::{println, stdio::stdout}; 2 | use std::fmt::Write; 3 | use std::panic; 4 | pub fn panic_hook(info: &panic::PanicHookInfo) { 5 | println!("panic! within WASM: {}", info.to_string()); 6 | } 7 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/imports.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "test-utils")] 2 | use alkanes_support::context::Context; 3 | #[cfg(feature = "test-utils")] 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[cfg(not(feature = "test-utils"))] 7 | #[link(wasm_import_module = "env")] 8 | extern "C" { 9 | pub fn abort(a: i32, b: i32, c: i32, d: i32); 10 | pub fn __load_storage(k: i32, v: i32) -> i32; 11 | pub fn __request_storage(k: i32) -> i32; 12 | pub fn __log(v: i32); 13 | pub fn __balance(who: i32, what: i32, output: i32); 14 | pub fn __request_context() -> i32; 15 | pub fn __load_context(output: i32) -> i32; 16 | pub fn __sequence(output: i32); 17 | pub fn __fuel(output: i32); 18 | pub fn __height(output: i32); 19 | pub fn __returndatacopy(output: i32); 20 | pub fn __request_transaction() -> i32; 21 | pub fn __load_transaction(output: i32); 22 | pub fn __request_block() -> i32; 23 | pub fn __load_block(output: i32); 24 | pub fn __call(cellpack: i32, incoming_alkanes: i32, checkpoint: i32, start_fuel: u64) -> i32; 25 | pub fn __staticcall( 26 | cellpack: i32, 27 | incoming_alkanes: i32, 28 | checkpoint: i32, 29 | start_fuel: u64, 30 | ) -> i32; 31 | pub fn __delegatecall( 32 | cellpack: i32, 33 | incoming_alkanes: i32, 34 | checkpoint: i32, 35 | start_fuel: u64, 36 | ) -> i32; 37 | } 38 | 39 | #[cfg(feature = "test-utils")] 40 | pub mod externs { 41 | pub use wasm_bindgen::prelude::*; 42 | #[wasm_bindgen(js_namespace = ["process", "stdout"])] 43 | extern "C" { 44 | pub fn write(s: &str); 45 | } 46 | } 47 | 48 | #[cfg(feature = "test-utils")] 49 | pub static mut _CONTEXT: Option = None; 50 | 51 | #[cfg(feature = "test-utils")] 52 | mod exports { 53 | pub use super::externs; 54 | use super::_CONTEXT; 55 | use { 56 | alkanes_support::context::Context, 57 | metashrew_support::{compat::to_passback_ptr, utils::ptr_to_vec}, 58 | }; 59 | pub fn set_mock_context(context: Context) { 60 | unsafe { 61 | _CONTEXT = Some(context); 62 | } 63 | } 64 | pub fn abort(a: i32, b: i32, c: i32, d: i32) -> i32 { 65 | panic!("abort"); 66 | } 67 | pub fn __load_storage(k: i32, v: i32) -> i32 { 68 | v 69 | } 70 | pub fn __request_storage(k: i32) -> i32 { 71 | 0 72 | } 73 | pub fn __log(ptr: i32) -> () { 74 | externs::write(format!("{}", String::from_utf8(ptr_to_vec(ptr)).unwrap()).as_str()); 75 | } 76 | pub fn __balance(who: i32, what: i32, output: i32) -> i32 { 77 | 0 78 | } 79 | pub fn __request_context() -> i32 { 80 | unsafe { 81 | match _CONTEXT.as_ref() { 82 | Some(v) => v.serialize().len() as i32, 83 | None => 0, 84 | } 85 | } 86 | } 87 | pub fn __load_context(output: i32) { 88 | unsafe { 89 | match _CONTEXT.as_ref() { 90 | Some(v) => { 91 | let mut bytes: Vec = v.serialize(); 92 | let len = bytes.len(); 93 | let bytes_ref: &mut [u8] = &mut bytes; 94 | (&mut std::slice::from_raw_parts_mut(output as usize as *mut u8, len)) 95 | .clone_from_slice(&*bytes_ref); 96 | } 97 | None => (), 98 | } 99 | } 100 | } 101 | pub fn __sequence(output: i32) {} 102 | pub fn __fuel(output: i32) {} 103 | pub fn __height(output: i32) {} 104 | pub fn __returndatacopy(output: i32) {} 105 | pub fn __request_transaction() -> i32 { 106 | 0 107 | } 108 | pub fn __load_transaction(output: i32) {} 109 | pub fn __request_block() -> i32 { 110 | 0 111 | } 112 | pub fn __load_block(output: i32) {} 113 | pub fn __call(cellpack: i32, incoming_alkanes: i32, checkpoint: i32, start_fuel: u64) -> i32 { 114 | 0 115 | } 116 | pub fn __staticcall( 117 | cellpack: i32, 118 | incoming_alkanes: i32, 119 | checkpoint: i32, 120 | start_fuel: u64, 121 | ) -> i32 { 122 | 0 123 | } 124 | pub fn __delegatecall( 125 | cellpack: i32, 126 | incoming_alkanes: i32, 127 | checkpoint: i32, 128 | start_fuel: u64, 129 | ) -> i32 { 130 | 0 131 | } 132 | } 133 | 134 | #[cfg(feature = "test-utils")] 135 | pub use exports::*; 136 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | #[cfg(feature = "panic-hook")] 3 | pub mod compat; 4 | pub mod imports; 5 | pub mod message; 6 | pub mod runtime; 7 | pub mod stdio; 8 | pub mod storage; 9 | pub mod token; 10 | pub use crate::stdio::stdout; 11 | 12 | #[macro_export] 13 | macro_rules! declare_alkane { 14 | (impl AlkaneResponder for $struct_name:ident { 15 | type Message = $message_type:ident; 16 | }) => { 17 | #[no_mangle] 18 | pub extern "C" fn __execute() -> i32 { 19 | use alkanes_runtime::runtime::AlkaneResponder; 20 | use alkanes_runtime::runtime::{handle_error, handle_success, prepare_response}; 21 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 22 | 23 | let mut context = $struct_name::default().context().unwrap(); 24 | let mut inputs = context.inputs.clone(); 25 | 26 | if inputs.is_empty() { 27 | let extended = handle_error("No opcode provided"); 28 | return alkanes_runtime::runtime::response_to_i32(extended); 29 | } 30 | 31 | let opcode = inputs[0]; 32 | inputs.remove(0); 33 | 34 | let result = match $message_type::from_opcode(opcode, inputs) { 35 | Ok(message) => message.dispatch(&$struct_name::default()), 36 | Err(err) => Err(anyhow::anyhow!("Failed to parse message: {}", err)), 37 | }; 38 | 39 | let extended = match result { 40 | Ok(res) => handle_success(res), 41 | Err(err) => { 42 | let error_msg = format!("Error: {}", err); 43 | let extended = handle_error(&error_msg); 44 | return alkanes_runtime::runtime::response_to_i32(extended); 45 | } 46 | }; 47 | 48 | alkanes_runtime::runtime::response_to_i32(extended) 49 | } 50 | 51 | #[no_mangle] 52 | pub extern "C" fn __meta() -> i32 { 53 | let abi = $message_type::export_abi(); 54 | export_bytes(&abi) 55 | } 56 | 57 | fn export_bytes(data: &[u8]) -> i32 { 58 | let response_bytes = to_arraybuffer_layout(data); 59 | Box::leak(Box::new(response_bytes)).as_mut_ptr() as usize as i32 + 4 60 | } 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/message.rs: -------------------------------------------------------------------------------- 1 | use alkanes_support::response::CallResponse; 2 | use anyhow::Result; 3 | 4 | // Re-export the MessageDispatch derive macro 5 | pub use alkanes_macros::MessageDispatch; 6 | 7 | /// Trait for dispatching messages based on opcodes 8 | pub trait MessageDispatch: Sized { 9 | /// Convert from an opcode and inputs to a message enum variant 10 | fn from_opcode(opcode: u128, inputs: Vec) -> Result; 11 | 12 | /// Dispatch the message to the appropriate method on the responder 13 | fn dispatch(&self, responder: &T) -> Result; 14 | 15 | /// Export ABI metadata for the message enum 16 | fn export_abi() -> Vec; 17 | } 18 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/meta.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | #[cfg(feature = "panic-hook")] 3 | pub mod compat; 4 | pub mod imports; 5 | pub mod runtime; 6 | pub mod stdio; 7 | pub mod storage; 8 | pub mod token; 9 | pub use crate::stdio::stdout; 10 | 11 | use metashrew_support::{compat::{to_arraybuffer_layout, to_passback_ptr}}; 12 | 13 | use serde::{Serialize, Deserialize}; 14 | 15 | #[derive(Serialize, Deserialize)] 16 | pub struct FunctionMetadata { 17 | pub name: String, 18 | String} 19 | 20 | #[derive(Serialize, Deserialize)] 21 | pub struct ApiMetadata { 22 | pub functions: Vec 23 | } 24 | 25 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/stdio.rs: -------------------------------------------------------------------------------- 1 | use crate::imports::__log; 2 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 3 | pub use std::fmt::{Error, Write}; 4 | 5 | pub struct Stdout(()); 6 | 7 | impl Write for Stdout { 8 | fn write_str(&mut self, s: &str) -> Result<(), Error> { 9 | let mut data = to_arraybuffer_layout::>(s.to_string().as_bytes().to_vec()); 10 | unsafe { 11 | __log(to_passback_ptr(&mut data)); 12 | } 13 | return Ok(()); 14 | } 15 | } 16 | 17 | pub fn stdout() -> Stdout { 18 | Stdout(()) 19 | } 20 | 21 | #[macro_export] 22 | macro_rules! println { 23 | ( $( $x:expr ),* ) => { 24 | { 25 | writeln!(stdout(), $($x),*).unwrap(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::runtime::AlkaneResponder; 2 | use anyhow::Result; 3 | 4 | use alkanes_support::response::CallResponse; 5 | use metashrew_support::index_pointer::KeyValuePointer; 6 | use std::sync::Arc; 7 | 8 | struct StorageHandle(()); 9 | 10 | impl AlkaneResponder for StorageHandle {} 11 | 12 | const RUNTIME_STORAGE: StorageHandle = StorageHandle(()); 13 | 14 | #[derive(Debug, Clone, Default)] 15 | pub struct StoragePointer(pub Arc>); 16 | 17 | #[allow(dead_code)] 18 | impl KeyValuePointer for StoragePointer { 19 | fn wrap(word: &Vec) -> StoragePointer { 20 | StoragePointer(Arc::>::new(word.clone())) 21 | } 22 | fn unwrap(&self) -> Arc> { 23 | self.0.clone() 24 | } 25 | fn inherits(&mut self, _v: &Self) {} 26 | fn set(&mut self, v: Arc>) { 27 | RUNTIME_STORAGE.store(self.unwrap().as_ref().clone(), v.as_ref().clone()) 28 | } 29 | fn get(&self) -> Arc> { 30 | Arc::new(RUNTIME_STORAGE.load(self.unwrap().as_ref().clone())) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/alkanes-runtime/src/token.rs: -------------------------------------------------------------------------------- 1 | pub trait Token { 2 | fn name(&self) -> String; 3 | fn symbol(&self) -> String; 4 | } 5 | -------------------------------------------------------------------------------- /crates/alkanes-std-auth-token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-auth-token" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | bitcoin = { workspace = true } 14 | metashrew-support = { workspace = true } 15 | protorune-support = { workspace = true } 16 | -------------------------------------------------------------------------------- /crates/alkanes-std-auth-token/src/lib.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::runtime::AlkaneResponder; 2 | use alkanes_runtime::{ 3 | declare_alkane, message::MessageDispatch, storage::StoragePointer, token::Token, 4 | }; 5 | #[allow(unused_imports)] 6 | use alkanes_runtime::{ 7 | println, 8 | stdio::{stdout, Write}, 9 | }; 10 | use alkanes_support::{context::Context, parcel::AlkaneTransfer, response::CallResponse}; 11 | use anyhow::{anyhow, Result}; 12 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 13 | use metashrew_support::index_pointer::KeyValuePointer; 14 | use std::sync::Arc; 15 | 16 | #[derive(Default)] 17 | pub struct AuthToken(()); 18 | 19 | impl Token for AuthToken { 20 | fn name(&self) -> String { 21 | String::from("AUTH") 22 | } 23 | fn symbol(&self) -> String { 24 | String::from("AUTH") 25 | } 26 | } 27 | 28 | #[derive(MessageDispatch)] 29 | enum AuthTokenMessage { 30 | #[opcode(0)] 31 | Initialize { amount: u128 }, 32 | 33 | #[opcode(1)] 34 | Authenticate, 35 | 36 | #[opcode(99)] 37 | #[returns(String)] 38 | GetName, 39 | 40 | #[opcode(100)] 41 | #[returns(String)] 42 | GetSymbol, 43 | } 44 | 45 | impl AuthToken { 46 | fn initialize(&self, amount: u128) -> Result { 47 | self.observe_initialization()?; 48 | let context = self.context()?; 49 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 50 | response.alkanes = context.incoming_alkanes.clone(); 51 | response.alkanes.0.push(AlkaneTransfer { 52 | id: context.myself.clone(), 53 | value: amount, 54 | }); 55 | Ok(response) 56 | } 57 | 58 | fn authenticate(&self) -> Result { 59 | let context = self.context()?; 60 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 61 | 62 | if context.incoming_alkanes.0.len() != 1 { 63 | return Err(anyhow!( 64 | "did not authenticate with only the authentication token" 65 | )); 66 | } 67 | let transfer = context.incoming_alkanes.0[0].clone(); 68 | if transfer.id != context.myself.clone() { 69 | return Err(anyhow!("supplied alkane is not authentication token")); 70 | } 71 | if transfer.value < 1 { 72 | return Err(anyhow!( 73 | "less than 1 unit of authentication token supplied to authenticate" 74 | )); 75 | } 76 | response.data = vec![0x01]; 77 | response.alkanes.0.push(transfer); 78 | Ok(response) 79 | } 80 | 81 | fn get_name(&self) -> Result { 82 | let context = self.context()?; 83 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 84 | 85 | response.data = self.name().into_bytes().to_vec(); 86 | Ok(response) 87 | } 88 | 89 | fn get_symbol(&self) -> Result { 90 | let context = self.context()?; 91 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 92 | 93 | response.data = self.symbol().into_bytes().to_vec(); 94 | Ok(response) 95 | } 96 | } 97 | 98 | impl AlkaneResponder for AuthToken {} 99 | 100 | // Use the new macro format 101 | declare_alkane! { 102 | impl AlkaneResponder for AuthToken { 103 | type Message = AuthTokenMessage; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/alkanes-std-factory-support/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-factory-support" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [features] 10 | test = [] 11 | 12 | [dependencies] 13 | alkanes-support = { workspace = true } 14 | alkanes-runtime = { workspace = true } 15 | metashrew-support = { workspace = true } 16 | ordinals = { workspace = true } 17 | anyhow = { workspace = true } 18 | bitcoin = { workspace = true } 19 | -------------------------------------------------------------------------------- /crates/alkanes-std-factory-support/src/lib.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::{runtime::AlkaneResponder, storage::StoragePointer}; 2 | use alkanes_support::response::CallResponse; 3 | use alkanes_support::utils::overflow_error; 4 | use alkanes_support::witness::find_witness_payload; 5 | use alkanes_support::{context::Context, parcel::AlkaneTransfer}; 6 | use anyhow::{anyhow, Result}; 7 | use bitcoin::Transaction; 8 | use metashrew_support::index_pointer::KeyValuePointer; 9 | use metashrew_support::utils::consensus_decode; 10 | use std::sync::Arc; 11 | 12 | fn name_pointer() -> StoragePointer { 13 | StoragePointer::from_keyword("/name") 14 | } 15 | 16 | fn symbol_pointer() -> StoragePointer { 17 | StoragePointer::from_keyword("/symbol") 18 | } 19 | 20 | pub fn trim(v: u128) -> String { 21 | String::from_utf8( 22 | v.to_le_bytes() 23 | .into_iter() 24 | .fold(Vec::::new(), |mut r, v| { 25 | if v != 0 { 26 | r.push(v) 27 | } 28 | r 29 | }), 30 | ) 31 | .unwrap() 32 | } 33 | 34 | pub struct ContextHandle(()); 35 | impl AlkaneResponder for ContextHandle {} 36 | 37 | pub const CONTEXT: ContextHandle = ContextHandle(()); 38 | 39 | pub trait MintableToken { 40 | fn name(&self) -> String { 41 | String::from_utf8(self.name_pointer().get().as_ref().clone()) 42 | .expect("name not saved as utf-8, did this deployment revert?") 43 | } 44 | fn symbol(&self) -> String { 45 | String::from_utf8(self.symbol_pointer().get().as_ref().clone()) 46 | .expect("symbol not saved as utf-8, did this deployment revert?") 47 | } 48 | fn set_name_and_symbol(&self, name: u128, symbol: u128) { 49 | self.set_string_field_from_u128(self.name_pointer(), name); 50 | self.set_string_field_from_u128(self.symbol_pointer(), symbol); 51 | } 52 | fn set_name_and_symbol_str(&self, name: String, symbol: String) { 53 | self.set_string_field(self.name_pointer(), name); 54 | self.set_string_field(self.symbol_pointer(), symbol); 55 | } 56 | fn name_pointer(&self) -> StoragePointer { 57 | name_pointer() 58 | } 59 | fn symbol_pointer(&self) -> StoragePointer { 60 | symbol_pointer() 61 | } 62 | fn set_string_field_from_u128(&self, mut pointer: StoragePointer, v: u128) { 63 | pointer.set(Arc::new(trim(v).as_bytes().to_vec())); 64 | } 65 | fn set_string_field(&self, mut pointer: StoragePointer, v: String) { 66 | pointer.set(Arc::new(v.as_bytes().to_vec())); 67 | } 68 | fn total_supply_pointer(&self) -> StoragePointer { 69 | StoragePointer::from_keyword("/totalsupply") 70 | } 71 | fn total_supply(&self) -> u128 { 72 | self.total_supply_pointer().get_value::() 73 | } 74 | fn set_total_supply(&self, v: u128) { 75 | self.total_supply_pointer().set_value::(v); 76 | } 77 | fn increase_total_supply(&self, v: u128) -> Result<()> { 78 | self.set_total_supply(overflow_error(self.total_supply().checked_add(v))?); 79 | Ok(()) 80 | } 81 | fn decrease_total_supply(&self, v: u128) -> Result<()> { 82 | self.set_total_supply(overflow_error(self.total_supply().checked_sub(v))?); 83 | Ok(()) 84 | } 85 | fn mint(&self, context: &Context, value: u128) -> Result { 86 | self.increase_total_supply(value)?; 87 | Ok(AlkaneTransfer { 88 | id: context.myself.clone(), 89 | value, 90 | }) 91 | } 92 | fn data_pointer(&self) -> StoragePointer { 93 | StoragePointer::from_keyword("/data") 94 | } 95 | fn data(&self) -> Vec { 96 | self.data_pointer().get().as_ref().clone() 97 | } 98 | fn set_data(&self) -> Result<()> { 99 | let tx = consensus_decode::(&mut std::io::Cursor::new(CONTEXT.transaction()))?; 100 | self.data_pointer() 101 | .set(Arc::new(find_witness_payload(&tx, 0).ok_or_else(|| { 102 | anyhow!("owned-token-factory: witness envelope at index 0 does not contain data") 103 | })?)); 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /crates/alkanes-std-genesis-alkane/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-genesis-alkane" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [features] 10 | regtest = [] 11 | dogecoin = [] 12 | luckycoin = [] 13 | mainnet = [] 14 | fractal = [] 15 | bellscoin = [] 16 | 17 | [dependencies] 18 | alkanes-runtime = { workspace = true } 19 | alkanes-support = { workspace = true } 20 | anyhow = { workspace = true } 21 | bitcoin = { workspace = true } 22 | hex = { workspace = true } 23 | metashrew-support = { workspace = true } 24 | protorune-support = { workspace = true } 25 | -------------------------------------------------------------------------------- /crates/alkanes-std-genesis-alkane/src/chain.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::runtime::AlkaneResponder; 2 | use alkanes_support::{response::CallResponse, utils::overflow_error}; 3 | use anyhow::Result; 4 | 5 | #[allow(unused_imports)] 6 | use alkanes_runtime::{ 7 | println, 8 | stdio::{stdout, Write}, 9 | }; 10 | 11 | pub struct ContextHandle(()); 12 | 13 | impl AlkaneResponder for ContextHandle {} 14 | 15 | pub const CONTEXT_HANDLE: ContextHandle = ContextHandle(()); 16 | 17 | pub trait ChainConfiguration { 18 | fn block_reward(&self, n: u64) -> u128; 19 | fn genesis_block(&self) -> u64; 20 | fn average_payout_from_genesis(&self) -> u128; 21 | fn premine(&self) -> Result { 22 | let blocks = 23 | overflow_error(CONTEXT_HANDLE.height().checked_sub(self.genesis_block()))? as u128; 24 | Ok(overflow_error( 25 | blocks.checked_mul(self.average_payout_from_genesis()), 26 | )?) 27 | } 28 | fn current_block_reward(&self) -> u128 { 29 | self.block_reward(CONTEXT_HANDLE.height()) 30 | } 31 | fn max_supply(&self) -> u128; 32 | } 33 | -------------------------------------------------------------------------------- /crates/alkanes-std-genesis-protorune/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-genesis-protorune" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | bitcoin = { workspace = true } 14 | hex = { workspace = true } 15 | metashrew-support = { workspace = true } 16 | protorune-support = { workspace = true } 17 | -------------------------------------------------------------------------------- /crates/alkanes-std-genesis-protorune/src/lib.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::declare_alkane; 2 | use alkanes_runtime::message::MessageDispatch; 3 | #[allow(unused_imports)] 4 | use alkanes_runtime::{ 5 | println, 6 | stdio::{stdout, Write}, 7 | }; 8 | use alkanes_runtime::{runtime::AlkaneResponder, storage::StoragePointer, token::Token}; 9 | use alkanes_support::{ 10 | context::Context, id::AlkaneId, parcel::AlkaneTransfer, response::CallResponse, 11 | }; 12 | use anyhow::{anyhow, Result}; 13 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 14 | use metashrew_support::index_pointer::KeyValuePointer; 15 | 16 | #[derive(Default)] 17 | pub struct GenesisProtorune(()); 18 | 19 | #[derive(MessageDispatch)] 20 | enum GenesisProtoruneMessage { 21 | #[opcode(0)] 22 | Initialize, 23 | 24 | #[opcode(77)] 25 | Mint, 26 | 27 | #[opcode(99)] 28 | #[returns(String)] 29 | GetName, 30 | 31 | #[opcode(100)] 32 | #[returns(String)] 33 | GetSymbol, 34 | 35 | #[opcode(101)] 36 | #[returns(u128)] 37 | GetTotalSupply, 38 | } 39 | 40 | impl Token for GenesisProtorune { 41 | fn name(&self) -> String { 42 | String::from("Genesis Protorune") 43 | } 44 | fn symbol(&self) -> String { 45 | String::from("aGP") 46 | } 47 | } 48 | 49 | impl GenesisProtorune { 50 | pub fn total_supply_pointer(&self) -> StoragePointer { 51 | StoragePointer::from_keyword("/totalsupply") 52 | } 53 | 54 | pub fn total_supply(&self) -> u128 { 55 | self.total_supply_pointer().get_value::() 56 | } 57 | 58 | pub fn set_total_supply(&self, v: u128) { 59 | self.total_supply_pointer().set_value::(v); 60 | } 61 | 62 | // Helper method that creates a mint transfer 63 | pub fn create_mint_transfer(&self, context: &Context) -> Result { 64 | if context.incoming_alkanes.0.len() != 1 65 | || &context.incoming_alkanes.0[0].id 66 | != &(AlkaneId { 67 | block: 849236, 68 | tx: 298, 69 | }) 70 | { 71 | panic!("can only mint in response to incoming QUORUM•GENESIS•PROTORUNE"); 72 | } 73 | let value = context.incoming_alkanes.0[0].value; 74 | let mut total_supply_pointer = self.total_supply_pointer(); 75 | total_supply_pointer.set_value::(total_supply_pointer.get_value::() + value); 76 | Ok(AlkaneTransfer { 77 | id: context.myself.clone(), 78 | value, 79 | }) 80 | } 81 | 82 | fn initialize(&self) -> Result { 83 | self.observe_initialization()?; 84 | let context = self.context()?; 85 | let mut response = CallResponse::forward(&context.incoming_alkanes); 86 | 87 | // No initialization logic 88 | 89 | Ok(response) 90 | } 91 | 92 | // Method that matches the MessageDispatch enum 93 | fn mint(&self) -> Result { 94 | let context = self.context()?; 95 | let mut response = CallResponse::forward(&context.incoming_alkanes); 96 | 97 | response 98 | .alkanes 99 | .0 100 | .push(self.create_mint_transfer(&context)?); 101 | 102 | Ok(response) 103 | } 104 | 105 | fn get_name(&self) -> Result { 106 | let context = self.context()?; 107 | let mut response = CallResponse::forward(&context.incoming_alkanes); 108 | 109 | response.data = self.name().into_bytes().to_vec(); 110 | 111 | Ok(response) 112 | } 113 | 114 | fn get_symbol(&self) -> Result { 115 | let context = self.context()?; 116 | let mut response = CallResponse::forward(&context.incoming_alkanes); 117 | 118 | response.data = self.symbol().into_bytes().to_vec(); 119 | 120 | Ok(response) 121 | } 122 | 123 | fn get_total_supply(&self) -> Result { 124 | let context = self.context()?; 125 | let mut response = CallResponse::forward(&context.incoming_alkanes); 126 | 127 | response.data = (&self.total_supply().to_le_bytes()).to_vec(); 128 | 129 | Ok(response) 130 | } 131 | } 132 | 133 | impl AlkaneResponder for GenesisProtorune {} 134 | 135 | // Use the new macro format 136 | declare_alkane! { 137 | impl AlkaneResponder for GenesisProtorune { 138 | type Message = GenesisProtoruneMessage; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /crates/alkanes-std-merkle-distributor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-merkle-distributor" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | bitcoin = { workspace = true } 14 | metashrew-support = { workspace = true } 15 | num = { workspace = true } 16 | protorune-support = { workspace = true } 17 | ordinals = { workspace = true } 18 | rs_merkle = "1.4.2" 19 | ruint = { workspace = true } 20 | -------------------------------------------------------------------------------- /crates/alkanes-std-orbital/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-orbital" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | bitcoin = { workspace = true } 14 | hex = { workspace = true } 15 | hex_lit = { workspace = true } 16 | metashrew-support = { workspace = true } 17 | protorune-support = { workspace = true } 18 | -------------------------------------------------------------------------------- /crates/alkanes-std-orbital/src/lib.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::declare_alkane; 2 | use alkanes_runtime::message::MessageDispatch; 3 | #[allow(unused_imports)] 4 | use alkanes_runtime::{ 5 | println, 6 | stdio::{stdout, Write}, 7 | }; 8 | use alkanes_runtime::{runtime::AlkaneResponder, storage::StoragePointer, token::Token}; 9 | use alkanes_support::{parcel::AlkaneTransfer, response::CallResponse}; 10 | use anyhow::{anyhow, Result}; 11 | use hex_lit::hex; 12 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 13 | use metashrew_support::index_pointer::KeyValuePointer; 14 | 15 | #[derive(Default)] 16 | pub struct Orbital(()); 17 | 18 | #[derive(MessageDispatch)] 19 | enum OrbitalMessage { 20 | #[opcode(0)] 21 | Initialize, 22 | 23 | #[opcode(99)] 24 | #[returns(String)] 25 | GetName, 26 | 27 | #[opcode(100)] 28 | #[returns(String)] 29 | GetSymbol, 30 | 31 | #[opcode(101)] 32 | #[returns(u128)] 33 | GetTotalSupply, 34 | 35 | #[opcode(1000)] 36 | #[returns(Vec)] 37 | GetData, 38 | } 39 | 40 | impl Token for Orbital { 41 | fn name(&self) -> String { 42 | String::from("NFT") 43 | } 44 | fn symbol(&self) -> String { 45 | String::from("NFT") 46 | } 47 | } 48 | 49 | impl Orbital { 50 | pub fn total_supply_pointer(&self) -> StoragePointer { 51 | StoragePointer::from_keyword("/totalsupply") 52 | } 53 | 54 | pub fn total_supply(&self) -> u128 { 55 | self.total_supply_pointer().get_value::() 56 | } 57 | 58 | pub fn set_total_supply(&self, v: u128) { 59 | self.total_supply_pointer().set_value::(v); 60 | } 61 | 62 | pub fn data(&self) -> Vec { 63 | // in this reference implementation, we return a 1x1 PNG 64 | // NFT data can be anything, however 65 | (&hex!("89504e470d0a1a0a0000000d494844520000000100000001010300000025db56ca00000003504c5445000000a77a3dda0000000174524e530040e6d8660000000a4944415408d76360000000020001e221bc330000000049454e44ae426082")).to_vec() 66 | } 67 | 68 | fn initialize(&self) -> Result { 69 | self.observe_initialization()?; 70 | let context = self.context()?; 71 | let mut response = CallResponse::forward(&context.incoming_alkanes); 72 | 73 | self.set_total_supply(1); 74 | response.alkanes.0.push(AlkaneTransfer { 75 | id: context.myself.clone(), 76 | value: 1u128, 77 | }); 78 | 79 | Ok(response) 80 | } 81 | 82 | fn get_name(&self) -> Result { 83 | let context = self.context()?; 84 | let mut response = CallResponse::forward(&context.incoming_alkanes); 85 | 86 | response.data = self.name().into_bytes().to_vec(); 87 | 88 | Ok(response) 89 | } 90 | 91 | fn get_symbol(&self) -> Result { 92 | let context = self.context()?; 93 | let mut response = CallResponse::forward(&context.incoming_alkanes); 94 | 95 | response.data = self.symbol().into_bytes().to_vec(); 96 | 97 | Ok(response) 98 | } 99 | 100 | fn get_total_supply(&self) -> Result { 101 | let context = self.context()?; 102 | let mut response = CallResponse::forward(&context.incoming_alkanes); 103 | 104 | response.data = (&self.total_supply().to_le_bytes()).to_vec(); 105 | 106 | Ok(response) 107 | } 108 | 109 | fn get_data(&self) -> Result { 110 | let context = self.context()?; 111 | let mut response = CallResponse::forward(&context.incoming_alkanes); 112 | 113 | response.data = self.data(); 114 | 115 | Ok(response) 116 | } 117 | } 118 | 119 | impl AlkaneResponder for Orbital {} 120 | 121 | // Use the new macro format 122 | declare_alkane! { 123 | impl AlkaneResponder for Orbital { 124 | type Message = OrbitalMessage; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /crates/alkanes-std-owned-token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-owned-token" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [features] 10 | test = [] 11 | 12 | [dependencies] 13 | alkanes-support = { workspace = true } 14 | alkanes-runtime = { workspace = true } 15 | metashrew-support = { workspace = true } 16 | protorune-support = { workspace = true } 17 | alkanes-std-factory-support = { workspace = true } 18 | ordinals = { workspace = true } 19 | anyhow = { workspace = true } 20 | bitcoin = { workspace = true } 21 | -------------------------------------------------------------------------------- /crates/alkanes-std-owned-token/src/lib.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::runtime::AlkaneResponder; 2 | use alkanes_runtime::{auth::AuthenticatedResponder, declare_alkane, message::MessageDispatch}; 3 | #[allow(unused_imports)] 4 | use alkanes_runtime::{ 5 | println, 6 | stdio::{stdout, Write}, 7 | }; 8 | use alkanes_std_factory_support::MintableToken; 9 | use alkanes_support::{context::Context, parcel::AlkaneTransfer, response::CallResponse}; 10 | use anyhow::{anyhow, Result}; 11 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 12 | 13 | #[derive(Default)] 14 | pub struct OwnedToken(()); 15 | 16 | impl MintableToken for OwnedToken {} 17 | 18 | impl AuthenticatedResponder for OwnedToken {} 19 | 20 | #[derive(MessageDispatch)] 21 | enum OwnedTokenMessage { 22 | #[opcode(0)] 23 | Initialize { 24 | auth_token_units: u128, 25 | token_units: u128, 26 | }, 27 | 28 | #[opcode(1)] 29 | InitializeWithNameSymbol { 30 | auth_token_units: u128, 31 | token_units: u128, 32 | name: String, 33 | symbol: String, 34 | }, 35 | 36 | #[opcode(77)] 37 | Mint { token_units: u128 }, 38 | 39 | #[opcode(99)] 40 | #[returns(String)] 41 | GetName, 42 | 43 | #[opcode(100)] 44 | #[returns(String)] 45 | GetSymbol, 46 | 47 | #[opcode(101)] 48 | #[returns(u128)] 49 | GetTotalSupply, 50 | 51 | #[opcode(1000)] 52 | #[returns(Vec)] 53 | GetData, 54 | } 55 | 56 | impl OwnedToken { 57 | fn initialize(&self, auth_token_units: u128, token_units: u128) -> Result { 58 | self.initialize_with_name_symbol( 59 | auth_token_units, 60 | token_units, 61 | String::from("OWNED"), 62 | String::from("OWNED"), 63 | ) 64 | } 65 | 66 | fn initialize_with_name_symbol( 67 | &self, 68 | auth_token_units: u128, 69 | token_units: u128, 70 | name: String, 71 | symbol: String, 72 | ) -> Result { 73 | self.observe_initialization()?; 74 | let context = self.context()?; 75 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 76 | 77 | ::set_name_and_symbol_str(self, name, symbol); 78 | 79 | response 80 | .alkanes 81 | .0 82 | .push(self.deploy_auth_token(auth_token_units)?); 83 | 84 | response.alkanes.0.push(AlkaneTransfer { 85 | id: context.myself.clone(), 86 | value: token_units, 87 | }); 88 | 89 | Ok(response) 90 | } 91 | 92 | fn mint(&self, token_units: u128) -> Result { 93 | let context = self.context()?; 94 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 95 | 96 | self.only_owner()?; 97 | 98 | // Call the mint method from the MintableToken trait 99 | let transfer = ::mint(self, &context, token_units)?; 100 | response.alkanes.0.push(transfer); 101 | 102 | Ok(response) 103 | } 104 | 105 | fn get_name(&self) -> Result { 106 | let context = self.context()?; 107 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 108 | 109 | response.data = self.name().into_bytes().to_vec(); 110 | 111 | Ok(response) 112 | } 113 | 114 | fn get_symbol(&self) -> Result { 115 | let context = self.context()?; 116 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 117 | 118 | response.data = self.symbol().into_bytes().to_vec(); 119 | 120 | Ok(response) 121 | } 122 | 123 | fn get_total_supply(&self) -> Result { 124 | let context = self.context()?; 125 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 126 | 127 | response.data = self.total_supply().to_le_bytes().to_vec(); 128 | 129 | Ok(response) 130 | } 131 | 132 | fn get_data(&self) -> Result { 133 | let context = self.context()?; 134 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes.clone()); 135 | 136 | response.data = self.data(); 137 | 138 | Ok(response) 139 | } 140 | } 141 | 142 | impl AlkaneResponder for OwnedToken {} 143 | 144 | // Use the new macro format 145 | declare_alkane! { 146 | impl AlkaneResponder for OwnedToken { 147 | type Message = OwnedTokenMessage; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crates/alkanes-std-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-proxy" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | bitcoin = { workspace = true } 14 | metashrew-support = { workspace = true } 15 | protorune-support = { workspace = true } 16 | -------------------------------------------------------------------------------- /crates/alkanes-std-test/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | 4 | [target.wasm32-unknown-unknown] 5 | runner = "wasm-bindgen-test-runner" 6 | -------------------------------------------------------------------------------- /crates/alkanes-std-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-test" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | hex = { workspace = true } 14 | metashrew-support = { workspace = true } 15 | sha2 = "0.10.8" 16 | -------------------------------------------------------------------------------- /crates/alkanes-std-upgradeable/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-std-upgradeable" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | alkanes-runtime = { workspace = true } 11 | alkanes-support = { workspace = true } 12 | anyhow = { workspace = true } 13 | bitcoin = { workspace = true } 14 | metashrew-support = { workspace = true } 15 | protorune-support = { workspace = true } 16 | -------------------------------------------------------------------------------- /crates/alkanes-std-upgradeable/src/lib.rs: -------------------------------------------------------------------------------- 1 | use alkanes_runtime::auth::AuthenticatedResponder; 2 | use alkanes_runtime::declare_alkane; 3 | use alkanes_runtime::message::MessageDispatch; 4 | #[allow(unused_imports)] 5 | use alkanes_runtime::{ 6 | println, 7 | stdio::{stdout, Write}, 8 | }; 9 | use alkanes_runtime::{runtime::AlkaneResponder, storage::StoragePointer}; 10 | use alkanes_support::{cellpack::Cellpack, id::AlkaneId, response::CallResponse}; 11 | use anyhow::{anyhow, Result}; 12 | use metashrew_support::compat::{to_arraybuffer_layout, to_passback_ptr}; 13 | use metashrew_support::index_pointer::KeyValuePointer; 14 | use std::sync::Arc; 15 | 16 | #[derive(Default)] 17 | pub struct Upgradeable(()); 18 | 19 | #[derive(MessageDispatch)] 20 | enum UpgradeableMessage { 21 | #[opcode(0x7fff)] 22 | Initialize { 23 | implementation: AlkaneId, 24 | auth_token_units: u128, 25 | }, 26 | 27 | #[opcode(0x7ffe)] 28 | Upgrade { implementation: AlkaneId }, 29 | 30 | #[opcode(0x7ffd)] 31 | Delegate { inputs: Vec }, 32 | } 33 | 34 | impl Upgradeable { 35 | pub fn alkane_pointer(&self) -> StoragePointer { 36 | StoragePointer::from_keyword("/implementation") 37 | } 38 | 39 | pub fn alkane(&self) -> Result { 40 | Ok(self.alkane_pointer().get().as_ref().clone().try_into()?) 41 | } 42 | 43 | pub fn set_alkane(&self, v: AlkaneId) { 44 | self.alkane_pointer() 45 | .set(Arc::new(>>::into(v))); 46 | } 47 | 48 | fn initialize(&self, implementation: AlkaneId, auth_token_units: u128) -> Result { 49 | self.observe_initialization()?; 50 | let context = self.context()?; 51 | 52 | self.set_alkane(implementation); 53 | let mut response: CallResponse = CallResponse::forward(&context.incoming_alkanes); 54 | 55 | response 56 | .alkanes 57 | .0 58 | .push(self.deploy_auth_token(auth_token_units)?); 59 | Ok(response) 60 | } 61 | 62 | fn upgrade(&self, implementation: AlkaneId) -> Result { 63 | let context = self.context()?; 64 | 65 | self.only_owner()?; 66 | 67 | self.set_alkane(implementation); 68 | Ok(CallResponse::forward(&context.incoming_alkanes)) 69 | } 70 | 71 | fn delegate(&self, inputs: Vec) -> Result { 72 | let context = self.context()?; 73 | let cellpack = Cellpack { 74 | target: self.alkane()?, 75 | inputs: inputs, 76 | }; 77 | self.delegatecall(&cellpack, &context.incoming_alkanes, self.fuel()) 78 | } 79 | } 80 | 81 | impl AuthenticatedResponder for Upgradeable {} 82 | 83 | impl AlkaneResponder for Upgradeable {} 84 | 85 | // Use the new macro format 86 | declare_alkane! { 87 | impl AlkaneResponder for Upgradeable { 88 | type Message = UpgradeableMessage; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/alkanes-support/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alkanes-support" 3 | version = "0.2.3" 4 | edition = "2021" 5 | description = "ALKANES support" 6 | license = "MIT" 7 | repository = "https://github.com/kungfuflex/alkanes-rs" 8 | resolver = "2" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [dependencies] 14 | anyhow = { workspace = true } 15 | protorune-support = { workspace = true } 16 | metashrew-support = { workspace = true } 17 | bitcoin = { workspace = true } 18 | serde = "1.0.213" 19 | flate2 = "1.0.34" 20 | protobuf = { workspace = true } 21 | 22 | [build-dependencies] 23 | anyhow = "1.0.90" 24 | protobuf-codegen = "3.4.0" 25 | flate2 = "1.0.34" 26 | protoc-rust = { version = "2.28.0" } 27 | protoc-bin-vendored = "3.0.0" 28 | hex = "0.4.3" -------------------------------------------------------------------------------- /crates/alkanes-support/build.rs: -------------------------------------------------------------------------------- 1 | use protobuf_codegen; 2 | use protoc_bin_vendored; 3 | fn main() { 4 | protobuf_codegen::Codegen::new() 5 | .protoc() 6 | .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) 7 | .out_dir("src/proto") 8 | .inputs(&["proto/alkanes.proto"]) 9 | .include("proto") 10 | .run() 11 | .expect("running protoc failed"); 12 | } 13 | -------------------------------------------------------------------------------- /crates/alkanes-support/proto/alkanes.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package alkanes; 3 | message uint128 { 4 | uint64 lo = 1; 5 | uint64 hi = 2; 6 | } 7 | 8 | message AlkaneId { 9 | uint128 block = 1; 10 | uint128 tx = 2; 11 | } 12 | 13 | message AlkaneTransfer { 14 | AlkaneId id = 1; 15 | uint128 value = 2; 16 | } 17 | 18 | message MultiSimulateRequest { 19 | repeated MessageContextParcel parcels = 1; 20 | } 21 | 22 | message MessageContextParcel { 23 | repeated AlkaneTransfer alkanes = 1; 24 | bytes transaction = 2; 25 | bytes block = 3; 26 | uint64 height = 4; 27 | uint32 txindex = 6; 28 | bytes calldata = 5; 29 | uint32 vout = 7; 30 | uint32 pointer = 8; 31 | uint32 refund_pointer = 9; 32 | } 33 | 34 | message KeyValuePair { 35 | bytes key = 1; 36 | bytes value = 2; 37 | } 38 | 39 | message ExtendedCallResponse { 40 | repeated AlkaneTransfer alkanes = 1; 41 | repeated KeyValuePair storage = 2; 42 | bytes data = 3; 43 | } 44 | 45 | message Context { 46 | AlkaneId myself = 1; 47 | AlkaneId caller = 2; 48 | repeated uint128 inputs = 3; 49 | uint32 vout = 4; 50 | repeated AlkaneTransfer incoming_alkanes = 5; 51 | } 52 | 53 | message TraceContext { 54 | Context inner = 1; 55 | uint64 fuel = 2; 56 | } 57 | 58 | enum AlkanesTraceCallType { 59 | NONE = 0; 60 | CALL = 1; 61 | DELEGATECALL = 2; 62 | STATICCALL = 3; 63 | } 64 | 65 | enum AlkanesTraceStatusFlag { 66 | SUCCESS = 0; 67 | FAILURE = 1; 68 | } 69 | 70 | message AlkanesEnterContext { 71 | AlkanesTraceCallType call_type = 1; 72 | TraceContext context = 2; 73 | } 74 | 75 | message AlkanesExitContext { 76 | AlkanesTraceStatusFlag status = 1; 77 | ExtendedCallResponse response = 2; 78 | } 79 | 80 | message AlkanesCreate { 81 | AlkaneId new_alkane = 1; 82 | } 83 | 84 | message AlkanesTraceEvent { 85 | oneof event { 86 | AlkanesEnterContext enter_context = 1; 87 | AlkanesExitContext exit_context = 2; 88 | AlkanesCreate create_alkane = 3; 89 | } 90 | } 91 | 92 | message AlkanesBlockEvent { 93 | AlkanesTrace traces = 1; 94 | Outpoint outpoint = 2; 95 | uint64 txindex = 3; 96 | } 97 | 98 | message AlkanesBlockTraceEvent { 99 | repeated AlkanesBlockEvent events = 1; 100 | } 101 | 102 | message AlkanesTrace { 103 | repeated AlkanesTraceEvent events = 1; 104 | } 105 | 106 | message SimulateResponse { 107 | ExtendedCallResponse execution = 1; 108 | uint64 gas_used = 2; 109 | string error = 3; 110 | } 111 | 112 | message MultiSimulateResponse { 113 | repeated SimulateResponse responses = 1; 114 | string error = 2; 115 | } 116 | 117 | message AlkaneInventoryRequest { 118 | AlkaneId id = 1; 119 | } 120 | 121 | message AlkaneIdToOutpointRequest { 122 | AlkaneId id = 1; 123 | } 124 | 125 | message AlkaneInventoryResponse { 126 | repeated AlkaneTransfer alkanes = 1; 127 | } 128 | 129 | message AlkaneIdToOutpointResponse { 130 | bytes txid = 1; 131 | uint32 vout = 2; 132 | } 133 | 134 | message Outpoint { 135 | bytes txid = 1; 136 | uint32 vout = 2; 137 | } 138 | 139 | message Trace { 140 | Outpoint outpoint = 1; 141 | AlkanesTrace trace = 2; 142 | } 143 | 144 | message TraceBlockRequest { 145 | uint64 block = 1; 146 | } 147 | 148 | message TraceBlockResponse { 149 | repeated Trace traces = 1; 150 | } 151 | 152 | message BytecodeRequest { 153 | AlkaneId id = 1; 154 | } 155 | 156 | message BlockRequest { 157 | uint32 height = 1; 158 | } 159 | 160 | message BlockResponse { 161 | bytes block = 1; 162 | uint32 height = 2; 163 | } 164 | -------------------------------------------------------------------------------- /crates/alkanes-support/proto/protorune.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package protorune; 4 | 5 | message RuneId { 6 | uint32 height = 1; 7 | uint32 txindex = 2; 8 | } 9 | 10 | message ProtoruneRuneId { 11 | uint128 height = 1; 12 | uint128 txindex = 2; 13 | } 14 | 15 | message Rune { 16 | ProtoruneRuneId runeId = 1; 17 | string name = 2; 18 | uint32 divisibility = 3; 19 | uint32 spacers = 4; 20 | string symbol = 5; 21 | uint32 runes_symbol = 6; 22 | } 23 | 24 | message BalanceSheetItem { 25 | Rune rune = 1; 26 | uint128 balance = 2; 27 | } 28 | 29 | message BalanceSheet { 30 | repeated BalanceSheetItem entries = 1; 31 | } 32 | 33 | message Outpoint { 34 | bytes txid = 1; 35 | uint32 vout = 2; 36 | } 37 | 38 | message OutpointWithProtocol { 39 | bytes txid = 1; 40 | uint32 vout = 2; 41 | uint128 protocol = 3; 42 | } 43 | message Output { 44 | bytes script = 1; 45 | uint64 value = 2; 46 | } 47 | 48 | message OutpointResponse { 49 | BalanceSheet balances = 1; 50 | Outpoint outpoint = 2; 51 | Output output = 3; 52 | uint32 height = 4; 53 | uint32 txindex = 5; 54 | } 55 | 56 | message PaginationInput { 57 | uint32 start = 1; 58 | uint32 end = 2; 59 | } 60 | 61 | message WalletRequest { 62 | bytes wallet = 1; 63 | } 64 | 65 | message WalletResponse { 66 | repeated OutpointResponse outpoints = 1; 67 | BalanceSheet balances = 2; 68 | } 69 | 70 | message ProtorunesWalletRequest { 71 | bytes wallet = 1; 72 | uint128 protocol_tag = 2; 73 | } 74 | 75 | message RunesByHeightRequest { 76 | uint64 height = 1; 77 | } 78 | 79 | message RunesResponse { 80 | repeated Rune runes = 1; 81 | } 82 | message ProtoBurn { 83 | uint128 protocol_tag = 1; 84 | uint32 pointer = 2; 85 | } 86 | 87 | message uint128 { 88 | uint64 lo = 1; 89 | uint64 hi = 2; 90 | } 91 | 92 | message Clause { 93 | ProtoruneRuneId rune = 1; 94 | uint128 amount = 2; 95 | } 96 | 97 | message Predicate { 98 | repeated Clause clauses = 1; 99 | } 100 | 101 | message ProtoMessage { 102 | bytes calldata = 1; 103 | Predicate predicate = 2; 104 | uint32 pointer = 3; 105 | uint32 refund_pointer = 4; 106 | } 107 | 108 | message RuntimeInput { 109 | uint128 protocol_tag = 1; 110 | } 111 | 112 | message Runtime { 113 | BalanceSheet balances = 1; 114 | } 115 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/cellpack.rs: -------------------------------------------------------------------------------- 1 | use crate::id::AlkaneId; 2 | use anyhow::Result; 3 | use metashrew_support::utils::consume_sized_int; 4 | use protorune_support::utils::encode_varint_list; 5 | use std::io::Cursor; 6 | 7 | #[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 8 | pub struct Cellpack { 9 | pub target: AlkaneId, 10 | pub inputs: Vec, 11 | } 12 | 13 | impl Cellpack { 14 | pub fn parse(cursor: &mut Cursor>) -> Result { 15 | let target = AlkaneId::parse(cursor)?; 16 | let mut result = Cellpack::default(); 17 | result.target = target; 18 | loop { 19 | match consume_sized_int::(cursor) { 20 | Ok(v) => result.inputs.push(v), 21 | Err(_) => { 22 | break; 23 | } 24 | } 25 | } 26 | Ok(result) 27 | } 28 | pub fn to_vec(&self) -> Vec { 29 | let mut values = Vec::::with_capacity(self.inputs.len() + 2); 30 | values.push(self.target.block); 31 | values.push(self.target.tx); 32 | values.extend(&self.inputs); 33 | values 34 | } 35 | pub fn serialize(&self) -> Vec { 36 | self.to_vec() 37 | .into_iter() 38 | .map(|v| (&v.to_le_bytes()).to_vec()) 39 | .flatten() 40 | .collect::>() 41 | } 42 | 43 | pub fn encipher(&self) -> Vec { 44 | // leb encode the list 45 | return encode_varint_list(&self.to_vec()); 46 | } 47 | 48 | // non LEB encipher if we ever need it 49 | // pub fn encipher(&self) -> Vec { 50 | // let mut values = Vec::::new(); 51 | // values.extend(self.target.block.to_le_bytes()); 52 | // values.extend(self.target.tx.to_le_bytes()); 53 | // let inputs_le: Vec = self 54 | // .inputs 55 | // .iter() 56 | // .flat_map(|&num| num.to_le_bytes()) // Convert each u128 to bytes 57 | // .collect(); 58 | // values.extend(inputs_le); 59 | // // leb encode the list 60 | // return values; 61 | // } 62 | } 63 | 64 | impl TryFrom> for Cellpack { 65 | type Error = anyhow::Error; 66 | fn try_from(v: Vec) -> std::result::Result { 67 | Ok(Cellpack { 68 | target: <[u128; 2] as TryFrom<&[u128]>>::try_from(&v[0..2])?.into(), 69 | inputs: (&v[2..]).to_vec(), 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const AMM_FACTORY_ID: u128 = 0xffef; 2 | pub const AUTH_TOKEN_FACTORY_ID: u128 = 0xffee; 3 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{id::AlkaneId, parcel::AlkaneTransferParcel}; 2 | use anyhow::Result; 3 | use metashrew_support::utils::consume_sized_int; 4 | use metashrew_support::utils::is_empty; 5 | use std::io::Cursor; 6 | 7 | #[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 8 | pub struct Context { 9 | pub myself: AlkaneId, 10 | pub caller: AlkaneId, 11 | pub vout: u32, 12 | pub incoming_alkanes: AlkaneTransferParcel, 13 | pub inputs: Vec, 14 | } 15 | 16 | impl Context { 17 | pub fn parse(v: &mut Cursor>) -> Result { 18 | let mut result = Context::default(); 19 | result.myself = AlkaneId::parse(v)?; 20 | result.caller = AlkaneId::parse(v)?; 21 | result.vout = consume_sized_int::(v)?.try_into()?; 22 | result.incoming_alkanes = AlkaneTransferParcel::parse(v)?; 23 | while !is_empty(v) { 24 | result.inputs.push(consume_sized_int::(v)?); 25 | } 26 | Ok(result) 27 | } 28 | pub fn serialize(&self) -> Vec { 29 | vec![] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/gz.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use flate2::read::GzDecoder; 3 | use flate2::write::GzEncoder; 4 | use flate2::Compression; 5 | use std::io::prelude::*; 6 | 7 | pub fn decompress(binary: Vec) -> Result> { 8 | let mut result = Vec::::new(); 9 | let mut reader = GzDecoder::new(&binary[..]); 10 | reader.read_to_end(&mut result)?; 11 | Ok(result) 12 | } 13 | 14 | pub fn compress(binary: Vec) -> Result> { 15 | let mut writer = GzEncoder::new(Vec::::with_capacity(binary.len()), Compression::best()); 16 | writer.write_all(&binary)?; 17 | Ok(writer.finish()?) 18 | } 19 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/id.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use metashrew_support::utils::consume_sized_int; 3 | use protorune_support::balance_sheet::ProtoruneRuneId; 4 | use std::hash::{Hash, Hasher}; 5 | 6 | #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 7 | pub struct AlkaneId { 8 | pub block: u128, 9 | pub tx: u128, 10 | } 11 | 12 | impl Into> for AlkaneId { 13 | fn into(self) -> Vec { 14 | (&[self.block, self.tx]).to_vec() 15 | } 16 | } 17 | 18 | impl TryFrom> for AlkaneId { 19 | type Error = anyhow::Error; 20 | fn try_from(v: Vec) -> Result { 21 | let mut cursor = std::io::Cursor::new(v); 22 | let block = consume_sized_int::(&mut cursor)?; 23 | let tx = consume_sized_int::(&mut cursor)?; 24 | Ok(Self::new(block, tx)) 25 | } 26 | } 27 | 28 | impl From for AlkaneId { 29 | fn from(id: ProtoruneRuneId) -> AlkaneId { 30 | AlkaneId { 31 | block: id.block, 32 | tx: id.tx, 33 | } 34 | } 35 | } 36 | 37 | impl Into for AlkaneId { 38 | fn into(self) -> ProtoruneRuneId { 39 | ProtoruneRuneId { 40 | block: self.block, 41 | tx: self.tx, 42 | } 43 | } 44 | } 45 | 46 | impl AlkaneId { 47 | pub fn parse(cursor: &mut std::io::Cursor>) -> Result { 48 | let (block, tx) = ( 49 | consume_sized_int::(cursor)?, 50 | consume_sized_int::(cursor)?, 51 | ); 52 | Ok(AlkaneId { block, tx }) 53 | } 54 | pub fn new(block: u128, tx: u128) -> AlkaneId { 55 | AlkaneId { block, tx } 56 | } 57 | pub fn is_created(&self, next_sequence: u128) -> bool { 58 | self.block == 2 && self.tx < next_sequence || self.block == 4 59 | } 60 | pub fn is_create(&self) -> bool { 61 | self.block == 1 && self.tx == 0 62 | } 63 | pub fn is_deployment(&self) -> bool { 64 | if self.block == 1 || self.block == 3 || self.block == 5 || self.block == 6 { 65 | true 66 | } else { 67 | false 68 | } 69 | } 70 | pub fn reserved(&self) -> Option { 71 | if self.block == 3 { 72 | Some(self.tx) 73 | } else { 74 | None 75 | } 76 | } 77 | pub fn factory(&self) -> Option { 78 | if self.block == 5 { 79 | Some(AlkaneId { 80 | block: 2, 81 | tx: self.tx, 82 | }) 83 | } else if self.block == 6 { 84 | Some(AlkaneId { 85 | block: 4, 86 | tx: self.tx, 87 | }) 88 | } else { 89 | None 90 | } 91 | } 92 | } 93 | 94 | impl From for Vec { 95 | fn from(rune_id: AlkaneId) -> Self { 96 | let mut bytes = Vec::::with_capacity(32); 97 | bytes.extend(&rune_id.block.to_le_bytes()); 98 | bytes.extend(&rune_id.tx.to_le_bytes()); 99 | bytes 100 | } 101 | } 102 | 103 | impl Into for [u128; 2] { 104 | fn into(self) -> AlkaneId { 105 | AlkaneId { 106 | block: self[0], 107 | tx: self[1], 108 | } 109 | } 110 | } 111 | 112 | impl From<&AlkaneId> for Vec { 113 | fn from(rune_id: &AlkaneId) -> Self { 114 | let mut bytes = Vec::::with_capacity(32); 115 | bytes.extend(&rune_id.block.to_le_bytes()); 116 | bytes.extend(&rune_id.tx.to_le_bytes()); 117 | bytes 118 | } 119 | } 120 | 121 | // Implement Hash trait for AlkaneId 122 | impl Hash for AlkaneId { 123 | fn hash(&self, state: &mut H) { 124 | self.block.hash(state); 125 | self.tx.hash(state); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/parcel.rs: -------------------------------------------------------------------------------- 1 | use crate::id::AlkaneId; 2 | use anyhow::Result; 3 | use metashrew_support::utils::consume_sized_int; 4 | use metashrew_support::{byte_view::ByteView, index_pointer::KeyValuePointer}; 5 | use protorune_support::balance_sheet::CachedBalanceSheet; 6 | use protorune_support::{balance_sheet::BalanceSheet, rune_transfer::RuneTransfer}; 7 | 8 | #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 9 | pub struct AlkaneTransfer { 10 | pub id: AlkaneId, 11 | pub value: u128, 12 | } 13 | 14 | impl From> for AlkaneTransferParcel { 15 | fn from(v: Vec) -> AlkaneTransferParcel { 16 | AlkaneTransferParcel( 17 | v.into_iter() 18 | .map(|incoming| AlkaneTransfer { 19 | id: incoming.id.into(), 20 | value: incoming.value, 21 | }) 22 | .collect(), 23 | ) 24 | } 25 | } 26 | 27 | impl Into for AlkaneTransfer { 28 | fn into(self) -> RuneTransfer { 29 | RuneTransfer { 30 | id: self.id.into(), 31 | value: self.value, 32 | } 33 | } 34 | } 35 | 36 | impl Into> for AlkaneTransferParcel { 37 | fn into(self) -> Vec { 38 | self.0.into_iter().map(|v| v.into()).collect() 39 | } 40 | } 41 | 42 | impl TryInto> for AlkaneTransferParcel { 43 | type Error = anyhow::Error; 44 | fn try_into(self) -> Result> { 45 | >>::into(self).try_into() 46 | } 47 | } 48 | 49 | impl TryInto for AlkaneTransferParcel { 50 | type Error = anyhow::Error; 51 | fn try_into(self) -> Result { 52 | >>::into(self).try_into() 53 | } 54 | } 55 | 56 | #[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 57 | pub struct AlkaneTransferParcel(pub Vec); 58 | 59 | impl AlkaneTransferParcel { 60 | pub fn parse(cursor: &mut std::io::Cursor>) -> Result { 61 | let mut result = AlkaneTransferParcel::default(); 62 | let len = consume_sized_int::(cursor)?; 63 | for _i in 0..len { 64 | result.0.push(AlkaneTransfer::parse(cursor)?); 65 | } 66 | Ok(result) 67 | } 68 | pub fn pay(&mut self, transfer: AlkaneTransfer) { 69 | self.0.push(transfer); 70 | } 71 | pub fn to_vec(&self) -> Vec { 72 | let len = self.0.len(); 73 | let mut buffer: Vec = Vec::::with_capacity(len * 3 + 1); 74 | buffer.push(len as u128); 75 | for v in self.0.clone() { 76 | let transfer_list: Vec = v.into(); 77 | buffer.extend(&transfer_list); 78 | } 79 | buffer 80 | } 81 | pub fn serialize(&self) -> Vec { 82 | let v = self 83 | .to_vec() 84 | .into_iter() 85 | .map(|v| (v.to_bytes())) 86 | .flatten() 87 | .collect::>(); 88 | v 89 | } 90 | } 91 | 92 | impl Into> for AlkaneTransfer { 93 | fn into(self) -> Vec { 94 | let mut buffer = Vec::::with_capacity(3); 95 | let id_ints: Vec = self.id.into(); 96 | buffer.extend(&id_ints); 97 | buffer.push(self.value); 98 | buffer 99 | } 100 | } 101 | 102 | impl AlkaneTransfer { 103 | pub fn parse(cursor: &mut std::io::Cursor>) -> Result { 104 | let id = AlkaneId::parse(cursor)?; 105 | let value = consume_sized_int::(cursor)?; 106 | Ok(AlkaneTransfer { id, value }) 107 | } 108 | pub fn to_vec(&self) -> Vec { 109 | >>::into(self.clone()) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | 3 | pub mod alkanes; 4 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/response.rs: -------------------------------------------------------------------------------- 1 | use crate::parcel::AlkaneTransferParcel; 2 | use crate::storage::StorageMap; 3 | use anyhow::Result; 4 | use metashrew_support::utils::consume_to_end; 5 | 6 | #[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 7 | pub struct CallResponse { 8 | pub alkanes: AlkaneTransferParcel, 9 | pub data: Vec, 10 | } 11 | 12 | impl CallResponse { 13 | pub fn parse(cursor: &mut std::io::Cursor>) -> Result { 14 | let parcel = AlkaneTransferParcel::parse(cursor)?; 15 | Ok(CallResponse { 16 | alkanes: parcel, 17 | data: consume_to_end(cursor)?, 18 | }) 19 | } 20 | pub fn serialize(&self) -> Vec { 21 | let mut list = Vec::>::new(); 22 | list.push(vec![self.alkanes.0.len() as u128]); 23 | self.alkanes 24 | .0 25 | .iter() 26 | .for_each(|v| list.push(vec![v.id.block, v.id.tx, v.value])); 27 | let mut result: Vec = list 28 | .into_iter() 29 | .flatten() 30 | .map(|v| (&v.to_le_bytes()).to_vec()) 31 | .flatten() 32 | .collect(); 33 | result.extend(&self.data); 34 | result 35 | } 36 | pub fn forward(incoming_alkanes: &AlkaneTransferParcel) -> CallResponse { 37 | let mut response = CallResponse::default(); 38 | response.alkanes = incoming_alkanes.clone(); 39 | response 40 | } 41 | } 42 | 43 | #[derive(Default, Clone, Debug, PartialEq, Eq)] 44 | pub struct ExtendedCallResponse { 45 | pub alkanes: AlkaneTransferParcel, 46 | pub storage: StorageMap, 47 | pub data: Vec, 48 | } 49 | 50 | impl Into for CallResponse { 51 | fn into(self) -> ExtendedCallResponse { 52 | ExtendedCallResponse { 53 | alkanes: self.alkanes, 54 | storage: StorageMap::default(), 55 | data: self.data, 56 | } 57 | } 58 | } 59 | 60 | impl Into for ExtendedCallResponse { 61 | fn into(self) -> CallResponse { 62 | CallResponse { 63 | alkanes: self.alkanes, 64 | data: self.data, 65 | } 66 | } 67 | } 68 | 69 | impl ExtendedCallResponse { 70 | pub fn serialize(&self) -> Vec { 71 | let mut list = Vec::>::new(); 72 | list.push(vec![self.alkanes.0.len() as u128]); 73 | self.alkanes 74 | .0 75 | .iter() 76 | .for_each(|v| list.push(vec![v.id.block, v.id.tx, v.value])); 77 | let mut result: Vec = list 78 | .into_iter() 79 | .flatten() 80 | .map(|v| (&v.to_le_bytes()).to_vec()) 81 | .flatten() 82 | .collect(); 83 | result.extend(&self.storage.serialize()); 84 | result.extend(&self.data); 85 | result 86 | } 87 | pub fn parse(cursor: &mut std::io::Cursor>) -> Result { 88 | let alkanes = AlkaneTransferParcel::parse(cursor)?; 89 | let storage = StorageMap::parse(cursor)?; 90 | let data = consume_to_end(cursor)?; 91 | Ok(Self { 92 | alkanes, 93 | storage, 94 | data, 95 | }) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use std::io::Cursor; 102 | 103 | use anyhow::Result; 104 | 105 | use crate::response::ExtendedCallResponse; 106 | #[test] 107 | pub fn test_serialize_deserialize() -> Result<()> { 108 | let mut response = ExtendedCallResponse::default(); 109 | response.data.push(1); 110 | let serialized = response.serialize(); 111 | let mut c = Cursor::new(serialized); 112 | let parsed = ExtendedCallResponse::parse(&mut c)?; 113 | assert_eq!(parsed, response); 114 | Ok(()) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/storage.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use metashrew_support::utils::{consume_exact, consume_sized_int}; 3 | use std::collections::BTreeMap; 4 | use std::io::Cursor; 5 | 6 | #[derive(Default, Clone, Debug, PartialEq, Eq)] 7 | pub struct StorageMap(pub BTreeMap, Vec>); 8 | 9 | impl FromIterator<(Vec, Vec)> for StorageMap { 10 | fn from_iter, Vec)>>(iter: I) -> Self { 11 | Self(BTreeMap::, Vec>::from_iter(iter)) 12 | } 13 | } 14 | 15 | impl StorageMap { 16 | pub fn parse(cursor: &mut Cursor>) -> Result { 17 | let mut pairs = Vec::<(Vec, Vec)>::new(); 18 | let len = consume_sized_int::(cursor)? as u64; 19 | 20 | for _i in 0..len { 21 | let key_length: usize = consume_sized_int::(cursor)?.try_into()?; 22 | let key: Vec = consume_exact(cursor, key_length)?; 23 | let value_length: usize = consume_sized_int::(cursor)?.try_into()?; 24 | let value: Vec = consume_exact(cursor, value_length)?; 25 | pairs.push((key, value)); 26 | } 27 | 28 | Ok(StorageMap::from_iter(pairs.into_iter())) 29 | } 30 | pub fn get>(&self, k: T) -> Option<&Vec> { 31 | self.0.get(k.as_ref()) 32 | } 33 | pub fn get_mut>(&mut self, k: T) -> Option<&mut Vec> { 34 | self.0.get_mut(k.as_ref()) 35 | } 36 | pub fn set, VT: AsRef<[u8]>>(&mut self, k: KT, v: VT) { 37 | self.0.insert(k.as_ref().to_vec(), v.as_ref().to_vec()); 38 | } 39 | pub fn serialize(&self) -> Vec { 40 | let mut buffer = Vec::::new(); 41 | let size = self.0.len() as u32; 42 | buffer.extend(&(size).to_le_bytes()); 43 | if size > 0 { 44 | for (k, v) in self.0.iter() { 45 | buffer.extend(&(k.len() as u32).to_le_bytes()); 46 | buffer.extend(k); 47 | buffer.extend(&(v.len() as u32).to_le_bytes()); 48 | buffer.extend(v); 49 | } 50 | } 51 | buffer 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/trace/block.rs: -------------------------------------------------------------------------------- 1 | use crate::trace::types::TraceEvent; 2 | use bitcoin::OutPoint; 3 | 4 | #[derive(Clone, Debug, Default)] 5 | pub struct BlockTraceItem { 6 | pub outpoint: OutPoint, 7 | pub trace: Vec, 8 | } 9 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/trace/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod types; 2 | pub use types::*; 3 | pub mod block; 4 | pub use block::*; 5 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::id::AlkaneId; 2 | use anyhow::{anyhow, Result}; 3 | use protobuf::MessageField; 4 | 5 | pub fn field_or_default, RT: Default>(v: MessageField) -> RT { 6 | v.into_option() 7 | .ok_or("") 8 | .and_then(|v| Ok(>::into(v))) 9 | .unwrap_or_else(|_| ::default()) 10 | } 11 | 12 | pub fn shift(v: &mut Vec) -> Option { 13 | if v.is_empty() { 14 | None 15 | } else { 16 | Some(v.remove(0)) 17 | } 18 | } 19 | 20 | pub fn shift_or_err(v: &mut Vec) -> Result { 21 | shift(v) 22 | .ok_or("") 23 | .map_err(|_| anyhow!("expected u128 value in list but list is exhausted")) 24 | } 25 | 26 | pub fn shift_id(v: &mut Vec) -> Option { 27 | let block = shift(v)?; 28 | let tx = shift(v)?; 29 | Some(AlkaneId { block, tx }) 30 | } 31 | 32 | pub fn shift_id_or_err(v: &mut Vec) -> Result { 33 | shift_id(v) 34 | .ok_or("") 35 | .map_err(|_| anyhow!("failed to shift AlkaneId from list")) 36 | } 37 | 38 | pub fn shift_as_long(v: &mut Vec) -> Option { 39 | Some(shift(v)?.try_into().ok()?) 40 | } 41 | 42 | pub fn shift_as_long_or_err(v: &mut Vec) -> Result { 43 | shift_as_long(v) 44 | .ok_or("") 45 | .map_err(|_| anyhow!("failed to shift u64 from list")) 46 | } 47 | 48 | pub fn overflow_error(v: Option) -> Result { 49 | v.ok_or("").map_err(|_| anyhow!("overflow error")) 50 | } 51 | 52 | /// A macro that captures the expression and passes it to overflow_error 53 | #[macro_export] 54 | macro_rules! checked_expr { 55 | ($expr:expr) => { 56 | match $expr { 57 | Some(val) => Ok(val), 58 | None => Err(anyhow::anyhow!(concat!( 59 | "Overflow error in expression: ", 60 | stringify!($expr) 61 | ))), 62 | } 63 | }; 64 | } 65 | 66 | pub fn shift_bytes32(v: &mut Vec) -> Option> { 67 | Some( 68 | (&[ 69 | shift_as_long(v)?, 70 | shift_as_long(v)?, 71 | shift_as_long(v)?, 72 | shift_as_long(v)?, 73 | ]) 74 | .to_vec() 75 | .into_iter() 76 | .rev() 77 | .fold(Vec::::new(), |mut r, v| { 78 | r.extend(&v.to_be_bytes()); 79 | r 80 | }), 81 | ) 82 | } 83 | 84 | pub fn shift_bytes32_or_err(v: &mut Vec) -> Result> { 85 | shift_bytes32(v) 86 | .ok_or("") 87 | .map_err(|_| anyhow!("failed to shift bytes32 from list")) 88 | } 89 | -------------------------------------------------------------------------------- /crates/alkanes-support/src/witness.rs: -------------------------------------------------------------------------------- 1 | use crate::envelope::RawEnvelope; 2 | use bitcoin::blockdata::transaction::Transaction; 3 | 4 | pub fn find_witness_payload(tx: &Transaction, i: usize) -> Option> { 5 | let envelopes = RawEnvelope::from_transaction(tx); 6 | if envelopes.len() <= i { 7 | None 8 | } else { 9 | Some( 10 | envelopes[i] 11 | .payload 12 | .clone() 13 | .into_iter() 14 | .skip(1) 15 | .flatten() 16 | .collect(), 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/ordinals/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ordinals" 3 | version = "0.2.3" 4 | edition = "2021" 5 | description = "Library for interoperating with ordinals and inscriptions" 6 | homepage = "https://github.com/ordinals/ord" 7 | repository = "https://github.com/ordinals/ord" 8 | license = "CC0-1.0" 9 | rust-version = "1.74.0" 10 | 11 | [features] 12 | dogecoin = [] 13 | 14 | [dependencies] 15 | bitcoin = { version = "0.32.4", features = ["rand"] } 16 | derive_more = { version = "1.0.0", features = ["display", "from_str"] } 17 | serde = { version = "1.0.137", features = ["derive"] } 18 | serde_with = "3.7.0" 19 | thiserror = "1.0.56" 20 | 21 | [dev-dependencies] 22 | serde_json = { version = "1.0.81", features = ["preserve_order"] } 23 | pretty_assertions = "1.2.1" 24 | -------------------------------------------------------------------------------- /crates/ordinals/src/artifact.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Serialize, Eq, PartialEq, Deserialize, Debug)] 4 | pub enum Artifact { 5 | Cenotaph(Cenotaph), 6 | Runestone(Runestone), 7 | } 8 | 9 | impl Artifact { 10 | pub fn mint(&self) -> Option { 11 | match self { 12 | Self::Cenotaph(cenotaph) => cenotaph.mint, 13 | Self::Runestone(runestone) => runestone.mint, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/ordinals/src/cenotaph.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Serialize, Eq, PartialEq, Deserialize, Debug, Default)] 4 | pub struct Cenotaph { 5 | pub etching: Option, 6 | pub flaw: Option, 7 | pub mint: Option, 8 | } 9 | -------------------------------------------------------------------------------- /crates/ordinals/src/charm.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, DeserializeFromStr, SerializeDisplay)] 4 | pub enum Charm { 5 | Coin = 0, 6 | Cursed = 1, 7 | Epic = 2, 8 | Legendary = 3, 9 | Lost = 4, 10 | Nineball = 5, 11 | Rare = 6, 12 | Reinscription = 7, 13 | Unbound = 8, 14 | Uncommon = 9, 15 | Vindicated = 10, 16 | Mythic = 11, 17 | Burned = 12, 18 | } 19 | 20 | impl Charm { 21 | pub const ALL: [Self; 13] = [ 22 | Self::Coin, 23 | Self::Uncommon, 24 | Self::Rare, 25 | Self::Epic, 26 | Self::Legendary, 27 | Self::Mythic, 28 | Self::Nineball, 29 | Self::Reinscription, 30 | Self::Cursed, 31 | Self::Unbound, 32 | Self::Lost, 33 | Self::Vindicated, 34 | Self::Burned, 35 | ]; 36 | 37 | fn flag(self) -> u16 { 38 | 1 << self as u16 39 | } 40 | 41 | pub fn set(self, charms: &mut u16) { 42 | *charms |= self.flag(); 43 | } 44 | 45 | pub fn is_set(self, charms: u16) -> bool { 46 | charms & self.flag() != 0 47 | } 48 | 49 | pub fn unset(self, charms: u16) -> u16 { 50 | charms & !self.flag() 51 | } 52 | 53 | pub fn icon(self) -> &'static str { 54 | match self { 55 | Self::Burned => "🔥", 56 | Self::Coin => "🪙", 57 | Self::Cursed => "👹", 58 | Self::Epic => "🪻", 59 | Self::Legendary => "🌝", 60 | Self::Lost => "🤔", 61 | Self::Mythic => "🎃", 62 | Self::Nineball => "\u{39}\u{fe0f}\u{20e3}", 63 | Self::Rare => "🧿", 64 | Self::Reinscription => "♻️", 65 | Self::Unbound => "🔓", 66 | Self::Uncommon => "🌱", 67 | Self::Vindicated => "\u{2764}\u{fe0f}\u{200d}\u{1f525}", 68 | } 69 | } 70 | 71 | pub fn charms(charms: u16) -> Vec { 72 | Self::ALL 73 | .into_iter() 74 | .filter(|charm| charm.is_set(charms)) 75 | .collect() 76 | } 77 | } 78 | 79 | impl Display for Charm { 80 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 81 | write!( 82 | f, 83 | "{}", 84 | match self { 85 | Self::Burned => "burned", 86 | Self::Coin => "coin", 87 | Self::Cursed => "cursed", 88 | Self::Epic => "epic", 89 | Self::Legendary => "legendary", 90 | Self::Lost => "lost", 91 | Self::Mythic => "mythic", 92 | Self::Nineball => "nineball", 93 | Self::Rare => "rare", 94 | Self::Reinscription => "reinscription", 95 | Self::Unbound => "unbound", 96 | Self::Uncommon => "uncommon", 97 | Self::Vindicated => "vindicated", 98 | } 99 | ) 100 | } 101 | } 102 | 103 | impl FromStr for Charm { 104 | type Err = String; 105 | 106 | fn from_str(s: &str) -> Result { 107 | Ok(match s { 108 | "burned" => Self::Burned, 109 | "coin" => Self::Coin, 110 | "cursed" => Self::Cursed, 111 | "epic" => Self::Epic, 112 | "legendary" => Self::Legendary, 113 | "lost" => Self::Lost, 114 | "mythic" => Self::Mythic, 115 | "nineball" => Self::Nineball, 116 | "rare" => Self::Rare, 117 | "reinscription" => Self::Reinscription, 118 | "unbound" => Self::Unbound, 119 | "uncommon" => Self::Uncommon, 120 | "vindicated" => Self::Vindicated, 121 | _ => return Err(format!("invalid charm `{s}`")), 122 | }) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::*; 129 | 130 | #[test] 131 | fn flag() { 132 | assert_eq!(Charm::Coin.flag(), 0b1); 133 | assert_eq!(Charm::Cursed.flag(), 0b10); 134 | } 135 | 136 | #[test] 137 | fn set() { 138 | let mut flags = 0; 139 | assert!(!Charm::Coin.is_set(flags)); 140 | Charm::Coin.set(&mut flags); 141 | assert!(Charm::Coin.is_set(flags)); 142 | } 143 | 144 | #[test] 145 | fn unset() { 146 | let mut flags = 0; 147 | Charm::Coin.set(&mut flags); 148 | assert!(Charm::Coin.is_set(flags)); 149 | let flags = Charm::Coin.unset(flags); 150 | assert!(!Charm::Coin.is_set(flags)); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /crates/ordinals/src/decimal_sat.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(PartialEq, Debug)] 4 | pub struct DecimalSat { 5 | pub height: Height, 6 | pub offset: u64, 7 | } 8 | 9 | impl From for DecimalSat { 10 | fn from(sat: Sat) -> Self { 11 | Self { 12 | height: sat.height(), 13 | offset: sat.third(), 14 | } 15 | } 16 | } 17 | 18 | impl Display for DecimalSat { 19 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 20 | write!(f, "{}.{}", self.height, self.offset) 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn decimal() { 30 | assert_eq!( 31 | Sat(0).decimal(), 32 | DecimalSat { 33 | height: Height(0), 34 | offset: 0 35 | } 36 | ); 37 | assert_eq!( 38 | Sat(1).decimal(), 39 | DecimalSat { 40 | height: Height(0), 41 | offset: 1 42 | } 43 | ); 44 | assert_eq!( 45 | Sat(2099999997689999).decimal(), 46 | DecimalSat { 47 | height: Height(6929999), 48 | offset: 0 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/ordinals/src/degree.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(PartialEq, Debug)] 4 | pub struct Degree { 5 | pub hour: u32, 6 | pub minute: u32, 7 | pub second: u32, 8 | pub third: u64, 9 | } 10 | 11 | impl Display for Degree { 12 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 13 | write!( 14 | f, 15 | "{}°{}′{}″{}‴", 16 | self.hour, self.minute, self.second, self.third 17 | ) 18 | } 19 | } 20 | 21 | impl From for Degree { 22 | fn from(sat: Sat) -> Self { 23 | let height = sat.height().n(); 24 | Degree { 25 | hour: height / (CYCLE_EPOCHS * SUBSIDY_HALVING_INTERVAL), 26 | minute: height % SUBSIDY_HALVING_INTERVAL, 27 | second: height % DIFFCHANGE_INTERVAL, 28 | third: sat.third(), 29 | } 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | 37 | fn case(sat: u64, hour: u32, minute: u32, second: u32, third: u64) { 38 | assert_eq!( 39 | Degree::from(Sat(sat)), 40 | Degree { 41 | hour, 42 | minute, 43 | second, 44 | third, 45 | } 46 | ); 47 | } 48 | 49 | #[test] 50 | fn from() { 51 | case(0, 0, 0, 0, 0); 52 | case(1, 0, 0, 0, 1); 53 | case(5_000_000_000, 0, 1, 1, 0); 54 | case( 55 | 5_000_000_000 * u64::from(DIFFCHANGE_INTERVAL), 56 | 0, 57 | DIFFCHANGE_INTERVAL, 58 | 0, 59 | 0, 60 | ); 61 | case( 62 | 5_000_000_000 * u64::from(SUBSIDY_HALVING_INTERVAL), 63 | 0, 64 | 0, 65 | 336, 66 | 0, 67 | ); 68 | case( 69 | (5_000_000_000 70 | + 2_500_000_000 71 | + 1_250_000_000 72 | + 625_000_000 73 | + 312_500_000 74 | + 156_250_000) 75 | * u64::from(SUBSIDY_HALVING_INTERVAL), 76 | 1, 77 | 0, 78 | 0, 79 | 0, 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/ordinals/src/edict.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq)] 4 | pub struct Edict { 5 | pub id: RuneId, 6 | pub amount: u128, 7 | pub output: u32, 8 | } 9 | 10 | impl Edict { 11 | pub fn from_integers( 12 | num_outputs: u32, 13 | id: RuneId, 14 | amount: u128, 15 | output: u128, 16 | check_outputs: bool, 17 | ) -> Option { 18 | let Ok(output) = u32::try_from(output) else { 19 | return None; 20 | }; 21 | 22 | // note that this allows `output == tx.output.len()`, which means to divide 23 | // amount between all non-OP_RETURN outputs 24 | if check_outputs && output > num_outputs { 25 | return None; 26 | } 27 | 28 | Some(Self { id, amount, output }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/ordinals/src/etching.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq)] 4 | pub struct Etching { 5 | pub divisibility: Option, 6 | pub premine: Option, 7 | pub rune: Option, 8 | pub spacers: Option, 9 | pub symbol: Option, 10 | pub terms: Option, 11 | pub turbo: bool, 12 | } 13 | 14 | impl Etching { 15 | pub const MAX_DIVISIBILITY: u8 = 38; 16 | pub const MAX_SPACERS: u32 = 0b00000111_11111111_11111111_11111111; 17 | 18 | pub fn supply(&self) -> Option { 19 | let premine = self.premine.unwrap_or_default(); 20 | let cap = self.terms.and_then(|terms| terms.cap).unwrap_or_default(); 21 | let amount = self 22 | .terms 23 | .and_then(|terms| terms.amount) 24 | .unwrap_or_default(); 25 | premine.checked_add(cap.checked_mul(amount)?) 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::*; 32 | 33 | #[test] 34 | fn max_spacers() { 35 | let mut rune = String::new(); 36 | 37 | for (i, c) in Rune(u128::MAX).to_string().chars().enumerate() { 38 | if i > 0 { 39 | rune.push('•'); 40 | } 41 | 42 | rune.push(c); 43 | } 44 | 45 | assert_eq!( 46 | Etching::MAX_SPACERS, 47 | rune.parse::().unwrap().spacers 48 | ); 49 | } 50 | 51 | #[test] 52 | fn supply() { 53 | #[track_caller] 54 | fn case(premine: Option, terms: Option, supply: Option) { 55 | assert_eq!( 56 | Etching { 57 | premine, 58 | terms, 59 | ..default() 60 | } 61 | .supply(), 62 | supply, 63 | ); 64 | } 65 | 66 | case(None, None, Some(0)); 67 | case(Some(0), None, Some(0)); 68 | case(Some(1), None, Some(1)); 69 | case( 70 | Some(1), 71 | Some(Terms { 72 | cap: None, 73 | amount: None, 74 | ..default() 75 | }), 76 | Some(1), 77 | ); 78 | 79 | case( 80 | None, 81 | Some(Terms { 82 | cap: None, 83 | amount: None, 84 | ..default() 85 | }), 86 | Some(0), 87 | ); 88 | 89 | case( 90 | Some(u128::MAX / 2 + 1), 91 | Some(Terms { 92 | cap: Some(u128::MAX / 2), 93 | amount: Some(1), 94 | ..default() 95 | }), 96 | Some(u128::MAX), 97 | ); 98 | 99 | case( 100 | Some(1000), 101 | Some(Terms { 102 | cap: Some(10), 103 | amount: Some(100), 104 | ..default() 105 | }), 106 | Some(2000), 107 | ); 108 | 109 | case( 110 | Some(u128::MAX), 111 | Some(Terms { 112 | cap: Some(1), 113 | amount: Some(1), 114 | ..default() 115 | }), 116 | None, 117 | ); 118 | 119 | case( 120 | Some(0), 121 | Some(Terms { 122 | cap: Some(1), 123 | amount: Some(u128::MAX), 124 | ..default() 125 | }), 126 | Some(u128::MAX), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /crates/ordinals/src/flaw.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 4 | #[serde(rename_all = "kebab-case")] 5 | pub enum Flaw { 6 | EdictOutput, 7 | EdictRuneId, 8 | InvalidScript, 9 | Opcode, 10 | SupplyOverflow, 11 | TrailingIntegers, 12 | TruncatedField, 13 | UnrecognizedEvenTag, 14 | UnrecognizedFlag, 15 | Varint, 16 | } 17 | 18 | impl Display for Flaw { 19 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 20 | match self { 21 | Self::EdictOutput => write!(f, "edict output greater than transaction output count"), 22 | Self::EdictRuneId => write!(f, "invalid rune ID in edict"), 23 | Self::InvalidScript => write!(f, "invalid script in OP_RETURN"), 24 | Self::Opcode => write!(f, "non-pushdata opcode in OP_RETURN"), 25 | Self::SupplyOverflow => write!(f, "supply overflows u128"), 26 | Self::TrailingIntegers => write!(f, "trailing integers in body"), 27 | Self::TruncatedField => write!(f, "field with missing value"), 28 | Self::UnrecognizedEvenTag => write!(f, "unrecognized even tag"), 29 | Self::UnrecognizedFlag => write!(f, "unrecognized field"), 30 | Self::Varint => write!(f, "invalid varint"), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/ordinals/src/height.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Display, FromStr)] 4 | pub struct Height(pub u32); 5 | 6 | impl Height { 7 | pub fn n(self) -> u32 { 8 | self.0 9 | } 10 | 11 | pub fn subsidy(self) -> u64 { 12 | Epoch::from(self).subsidy() 13 | } 14 | 15 | pub fn starting_sat(self) -> Sat { 16 | let epoch = Epoch::from(self); 17 | let epoch_starting_sat = epoch.starting_sat(); 18 | let epoch_starting_height = epoch.starting_height(); 19 | epoch_starting_sat + u64::from(self.n() - epoch_starting_height.n()) * epoch.subsidy() 20 | } 21 | 22 | pub fn period_offset(self) -> u32 { 23 | self.0 % DIFFCHANGE_INTERVAL 24 | } 25 | } 26 | 27 | impl Add for Height { 28 | type Output = Self; 29 | 30 | fn add(self, other: u32) -> Height { 31 | Self(self.0 + other) 32 | } 33 | } 34 | 35 | impl Sub for Height { 36 | type Output = Self; 37 | 38 | fn sub(self, other: u32) -> Height { 39 | Self(self.0 - other) 40 | } 41 | } 42 | 43 | impl PartialEq for Height { 44 | fn eq(&self, other: &u32) -> bool { 45 | self.0 == *other 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | 53 | #[test] 54 | fn n() { 55 | assert_eq!(Height(0).n(), 0); 56 | assert_eq!(Height(1).n(), 1); 57 | } 58 | 59 | #[test] 60 | fn add() { 61 | assert_eq!(Height(0) + 1, 1); 62 | assert_eq!(Height(1) + 100, 101); 63 | } 64 | 65 | #[test] 66 | fn sub() { 67 | assert_eq!(Height(1) - 1, 0); 68 | assert_eq!(Height(100) - 50, 50); 69 | } 70 | 71 | #[test] 72 | fn eq() { 73 | assert_eq!(Height(0), 0); 74 | assert_eq!(Height(100), 100); 75 | } 76 | 77 | #[test] 78 | fn from_str() { 79 | assert_eq!("0".parse::().unwrap(), 0); 80 | assert!("foo".parse::().is_err()); 81 | } 82 | 83 | #[test] 84 | fn subsidy() { 85 | assert_eq!(Height(0).subsidy(), 5000000000); 86 | assert_eq!(Height(1).subsidy(), 5000000000); 87 | assert_eq!(Height(SUBSIDY_HALVING_INTERVAL - 1).subsidy(), 5000000000); 88 | assert_eq!(Height(SUBSIDY_HALVING_INTERVAL).subsidy(), 2500000000); 89 | assert_eq!(Height(SUBSIDY_HALVING_INTERVAL + 1).subsidy(), 2500000000); 90 | } 91 | 92 | #[test] 93 | fn starting_sat() { 94 | assert_eq!(Height(0).starting_sat(), 0); 95 | assert_eq!(Height(1).starting_sat(), 5000000000); 96 | assert_eq!( 97 | Height(SUBSIDY_HALVING_INTERVAL - 1).starting_sat(), 98 | (u64::from(SUBSIDY_HALVING_INTERVAL) - 1) * 5000000000 99 | ); 100 | assert_eq!( 101 | Height(SUBSIDY_HALVING_INTERVAL).starting_sat(), 102 | u64::from(SUBSIDY_HALVING_INTERVAL) * 5000000000 103 | ); 104 | assert_eq!( 105 | Height(SUBSIDY_HALVING_INTERVAL + 1).starting_sat(), 106 | u64::from(SUBSIDY_HALVING_INTERVAL) * 5000000000 + 2500000000 107 | ); 108 | assert_eq!( 109 | Height(u32::MAX).starting_sat(), 110 | *Epoch::STARTING_SATS.last().unwrap() 111 | ); 112 | } 113 | 114 | #[test] 115 | fn period_offset() { 116 | assert_eq!(Height(0).period_offset(), 0); 117 | assert_eq!(Height(1).period_offset(), 1); 118 | assert_eq!(Height(DIFFCHANGE_INTERVAL - 1).period_offset(), 2015); 119 | assert_eq!(Height(DIFFCHANGE_INTERVAL).period_offset(), 0); 120 | assert_eq!(Height(DIFFCHANGE_INTERVAL + 1).period_offset(), 1); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/ordinals/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Types for interoperating with ordinals, inscriptions, and runes. 2 | #![allow(clippy::large_enum_variant)] 3 | 4 | use { 5 | bitcoin::{ 6 | consensus::{Decodable, Encodable}, 7 | constants::{DIFFCHANGE_INTERVAL, MAX_SCRIPT_ELEMENT_SIZE, SUBSIDY_HALVING_INTERVAL}, 8 | opcodes, 9 | script::{self, Instruction}, 10 | Network, OutPoint, ScriptBuf, Transaction, 11 | }, 12 | derive_more::{Display, FromStr}, 13 | serde::{Deserialize, Serialize}, 14 | serde_with::{DeserializeFromStr, SerializeDisplay}, 15 | std::{ 16 | cmp, 17 | collections::{BTreeMap, VecDeque}, 18 | fmt::{self, Formatter}, 19 | num::ParseIntError, 20 | ops::{Add, AddAssign, Sub}, 21 | }, 22 | thiserror::Error, 23 | }; 24 | 25 | pub const COIN_VALUE: u64 = 100_000_000; 26 | 27 | pub use { 28 | artifact::Artifact, cenotaph::Cenotaph, charm::Charm, decimal_sat::DecimalSat, degree::Degree, 29 | edict::Edict, epoch::Epoch, etching::Etching, flaw::Flaw, height::Height, pile::Pile, 30 | rarity::Rarity, rune::Rune, rune_id::RuneId, runestone::Runestone, sat::Sat, 31 | sat_point::SatPoint, spaced_rune::SpacedRune, terms::Terms, 32 | }; 33 | 34 | pub const CYCLE_EPOCHS: u32 = 6; 35 | 36 | fn default() -> T { 37 | Default::default() 38 | } 39 | 40 | mod artifact; 41 | mod cenotaph; 42 | mod charm; 43 | mod decimal_sat; 44 | mod degree; 45 | mod edict; 46 | mod epoch; 47 | mod etching; 48 | mod flaw; 49 | mod height; 50 | mod pile; 51 | mod rarity; 52 | mod rune; 53 | mod rune_id; 54 | pub mod runestone; 55 | pub mod sat; 56 | pub mod sat_point; 57 | pub mod spaced_rune; 58 | mod terms; 59 | pub mod varint; 60 | -------------------------------------------------------------------------------- /crates/ordinals/src/pile.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)] 4 | pub struct Pile { 5 | pub amount: u128, 6 | pub divisibility: u8, 7 | pub symbol: Option, 8 | } 9 | 10 | impl Display for Pile { 11 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 12 | let cutoff = 10u128.checked_pow(self.divisibility.into()).unwrap(); 13 | 14 | let whole = self.amount / cutoff; 15 | let mut fractional = self.amount % cutoff; 16 | 17 | if fractional == 0 { 18 | write!(f, "{whole}")?; 19 | } else { 20 | let mut width = usize::from(self.divisibility); 21 | while fractional % 10 == 0 { 22 | fractional /= 10; 23 | width -= 1; 24 | } 25 | 26 | write!(f, "{whole}.{fractional:0>width$}")?; 27 | } 28 | 29 | write!(f, "\u{A0}{}", self.symbol.unwrap_or('¤'))?; 30 | 31 | Ok(()) 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn display() { 41 | assert_eq!( 42 | Pile { 43 | amount: 0, 44 | divisibility: 0, 45 | symbol: None, 46 | } 47 | .to_string(), 48 | "0\u{A0}¤" 49 | ); 50 | assert_eq!( 51 | Pile { 52 | amount: 25, 53 | divisibility: 0, 54 | symbol: None, 55 | } 56 | .to_string(), 57 | "25\u{A0}¤" 58 | ); 59 | assert_eq!( 60 | Pile { 61 | amount: 0, 62 | divisibility: 1, 63 | symbol: None, 64 | } 65 | .to_string(), 66 | "0\u{A0}¤" 67 | ); 68 | assert_eq!( 69 | Pile { 70 | amount: 1, 71 | divisibility: 1, 72 | symbol: None, 73 | } 74 | .to_string(), 75 | "0.1\u{A0}¤" 76 | ); 77 | assert_eq!( 78 | Pile { 79 | amount: 1, 80 | divisibility: 2, 81 | symbol: None, 82 | } 83 | .to_string(), 84 | "0.01\u{A0}¤" 85 | ); 86 | assert_eq!( 87 | Pile { 88 | amount: 10, 89 | divisibility: 2, 90 | symbol: None, 91 | } 92 | .to_string(), 93 | "0.1\u{A0}¤" 94 | ); 95 | assert_eq!( 96 | Pile { 97 | amount: 1100, 98 | divisibility: 3, 99 | symbol: None, 100 | } 101 | .to_string(), 102 | "1.1\u{A0}¤" 103 | ); 104 | assert_eq!( 105 | Pile { 106 | amount: 100, 107 | divisibility: 2, 108 | symbol: None, 109 | } 110 | .to_string(), 111 | "1\u{A0}¤" 112 | ); 113 | assert_eq!( 114 | Pile { 115 | amount: 101, 116 | divisibility: 2, 117 | symbol: None, 118 | } 119 | .to_string(), 120 | "1.01\u{A0}¤" 121 | ); 122 | assert_eq!( 123 | Pile { 124 | amount: u128::MAX, 125 | divisibility: 18, 126 | symbol: None, 127 | } 128 | .to_string(), 129 | "340282366920938463463.374607431768211455\u{A0}¤" 130 | ); 131 | assert_eq!( 132 | Pile { 133 | amount: u128::MAX, 134 | divisibility: 38, 135 | symbol: None, 136 | } 137 | .to_string(), 138 | "3.40282366920938463463374607431768211455\u{A0}¤" 139 | ); 140 | assert_eq!( 141 | Pile { 142 | amount: 0, 143 | divisibility: 0, 144 | symbol: Some('$'), 145 | } 146 | .to_string(), 147 | "0\u{A0}$" 148 | ); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/ordinals/src/rune_id.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive( 4 | Debug, 5 | PartialEq, 6 | Copy, 7 | Clone, 8 | Hash, 9 | Eq, 10 | Ord, 11 | PartialOrd, 12 | Default, 13 | DeserializeFromStr, 14 | SerializeDisplay, 15 | )] 16 | pub struct RuneId { 17 | pub block: u64, 18 | pub tx: u32, 19 | } 20 | 21 | impl RuneId { 22 | pub fn new(block: u64, tx: u32) -> Option { 23 | let id = RuneId { block, tx }; 24 | 25 | if id.block == 0 && id.tx > 0 { 26 | return None; 27 | } 28 | 29 | Some(id) 30 | } 31 | 32 | pub fn delta(self, next: RuneId) -> Option<(u128, u128)> { 33 | let block = next.block.checked_sub(self.block)?; 34 | 35 | let tx = if block == 0 { 36 | next.tx.checked_sub(self.tx)? 37 | } else { 38 | next.tx 39 | }; 40 | 41 | Some((block.into(), tx.into())) 42 | } 43 | 44 | pub fn next(self: RuneId, block: u128, tx: u128) -> Option { 45 | RuneId::new( 46 | self.block.checked_add(block.try_into().ok()?)?, 47 | if block == 0 { 48 | self.tx.checked_add(tx.try_into().ok()?)? 49 | } else { 50 | tx.try_into().ok()? 51 | }, 52 | ) 53 | } 54 | } 55 | 56 | impl Display for RuneId { 57 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 58 | write!(f, "{}:{}", self.block, self.tx) 59 | } 60 | } 61 | 62 | impl FromStr for RuneId { 63 | type Err = Error; 64 | 65 | fn from_str(s: &str) -> Result { 66 | let (height, index) = s.split_once(':').ok_or(Error::Separator)?; 67 | 68 | Ok(Self { 69 | block: height.parse().map_err(Error::Block)?, 70 | tx: index.parse().map_err(Error::Transaction)?, 71 | }) 72 | } 73 | } 74 | 75 | #[derive(Debug, PartialEq)] 76 | pub enum Error { 77 | Separator, 78 | Block(ParseIntError), 79 | Transaction(ParseIntError), 80 | } 81 | 82 | impl Display for Error { 83 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 84 | match self { 85 | Self::Separator => write!(f, "missing separator"), 86 | Self::Block(err) => write!(f, "invalid height: {err}"), 87 | Self::Transaction(err) => write!(f, "invalid index: {err}"), 88 | } 89 | } 90 | } 91 | 92 | impl std::error::Error for Error {} 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | 98 | #[test] 99 | fn delta() { 100 | let mut expected = [ 101 | RuneId { block: 3, tx: 1 }, 102 | RuneId { block: 4, tx: 2 }, 103 | RuneId { block: 1, tx: 2 }, 104 | RuneId { block: 1, tx: 1 }, 105 | RuneId { block: 3, tx: 1 }, 106 | RuneId { block: 2, tx: 0 }, 107 | ]; 108 | 109 | expected.sort(); 110 | 111 | assert_eq!( 112 | expected, 113 | [ 114 | RuneId { block: 1, tx: 1 }, 115 | RuneId { block: 1, tx: 2 }, 116 | RuneId { block: 2, tx: 0 }, 117 | RuneId { block: 3, tx: 1 }, 118 | RuneId { block: 3, tx: 1 }, 119 | RuneId { block: 4, tx: 2 }, 120 | ] 121 | ); 122 | 123 | let mut previous = RuneId::default(); 124 | let mut deltas = Vec::new(); 125 | for id in expected { 126 | deltas.push(previous.delta(id).unwrap()); 127 | previous = id; 128 | } 129 | 130 | assert_eq!(deltas, [(1, 1), (0, 1), (1, 0), (1, 1), (0, 0), (1, 2)]); 131 | 132 | let mut previous = RuneId::default(); 133 | let mut actual = Vec::new(); 134 | for (block, tx) in deltas { 135 | let next = previous.next(block, tx).unwrap(); 136 | actual.push(next); 137 | previous = next; 138 | } 139 | 140 | assert_eq!(actual, expected); 141 | } 142 | 143 | #[test] 144 | fn display() { 145 | assert_eq!(RuneId { block: 1, tx: 2 }.to_string(), "1:2"); 146 | } 147 | 148 | #[test] 149 | fn from_str() { 150 | assert!(matches!("123".parse::(), Err(Error::Separator))); 151 | assert!(matches!(":".parse::(), Err(Error::Block(_)))); 152 | assert!(matches!("1:".parse::(), Err(Error::Transaction(_)))); 153 | assert!(matches!(":2".parse::(), Err(Error::Block(_)))); 154 | assert!(matches!("a:2".parse::(), Err(Error::Block(_)))); 155 | assert!(matches!( 156 | "1:a".parse::(), 157 | Err(Error::Transaction(_)), 158 | )); 159 | assert_eq!("1:2".parse::().unwrap(), RuneId { block: 1, tx: 2 }); 160 | } 161 | 162 | #[test] 163 | fn serde() { 164 | let rune_id = RuneId { block: 1, tx: 2 }; 165 | let json = "\"1:2\""; 166 | assert_eq!(serde_json::to_string(&rune_id).unwrap(), json); 167 | assert_eq!(serde_json::from_str::(json).unwrap(), rune_id); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /crates/ordinals/src/runestone/flag.rs: -------------------------------------------------------------------------------- 1 | pub(super) enum Flag { 2 | Etching = 0, 3 | Terms = 1, 4 | Turbo = 2, 5 | #[allow(unused)] 6 | Cenotaph = 127, 7 | } 8 | 9 | impl Flag { 10 | pub(super) fn mask(self) -> u128 { 11 | 1 << self as u128 12 | } 13 | 14 | pub(super) fn take(self, flags: &mut u128) -> bool { 15 | let mask = self.mask(); 16 | let set = *flags & mask != 0; 17 | *flags &= !mask; 18 | set 19 | } 20 | 21 | pub(super) fn set(self, flags: &mut u128) { 22 | *flags |= self.mask() 23 | } 24 | } 25 | 26 | impl From for u128 { 27 | fn from(flag: Flag) -> Self { 28 | flag.mask() 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn mask() { 38 | assert_eq!(Flag::Etching.mask(), 0b1); 39 | assert_eq!(Flag::Cenotaph.mask(), 1 << 127); 40 | } 41 | 42 | #[test] 43 | fn take() { 44 | let mut flags = 1; 45 | assert!(Flag::Etching.take(&mut flags)); 46 | assert_eq!(flags, 0); 47 | 48 | let mut flags = 0; 49 | assert!(!Flag::Etching.take(&mut flags)); 50 | assert_eq!(flags, 0); 51 | } 52 | 53 | #[test] 54 | fn set() { 55 | let mut flags = 0; 56 | Flag::Etching.set(&mut flags); 57 | assert_eq!(flags, 1); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/ordinals/src/runestone/message.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub struct Message { 4 | pub flaw: Option, 5 | pub edicts: Vec, 6 | pub fields: BTreeMap>, 7 | } 8 | 9 | impl Message { 10 | pub fn from_integers(num_outputs: u32, payload: &[u128], check_outputs: bool) -> Self { 11 | let mut edicts = Vec::new(); 12 | let mut fields = BTreeMap::>::new(); 13 | let mut flaw = None; 14 | 15 | for i in (0..payload.len()).step_by(2) { 16 | let tag = payload[i]; 17 | 18 | if Tag::Body == tag { 19 | let mut id = RuneId::default(); 20 | for chunk in payload[i + 1..].chunks(4) { 21 | if chunk.len() != 4 { 22 | flaw.get_or_insert(Flaw::TrailingIntegers); 23 | break; 24 | } 25 | 26 | let Some(next) = id.next(chunk[0], chunk[1]) else { 27 | flaw.get_or_insert(Flaw::EdictRuneId); 28 | break; 29 | }; 30 | 31 | let Some(edict) = 32 | Edict::from_integers(num_outputs, next, chunk[2], chunk[3], check_outputs) 33 | else { 34 | flaw.get_or_insert(Flaw::EdictOutput); 35 | break; 36 | }; 37 | 38 | id = next; 39 | edicts.push(edict); 40 | } 41 | break; 42 | } 43 | 44 | let Some(&value) = payload.get(i + 1) else { 45 | flaw.get_or_insert(Flaw::TruncatedField); 46 | break; 47 | }; 48 | 49 | fields.entry(tag).or_default().push_back(value); 50 | } 51 | 52 | Self { 53 | flaw, 54 | edicts, 55 | fields, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/ordinals/src/runestone/tag.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub enum Tag { 5 | Body = 0, 6 | Flags = 2, 7 | Rune = 4, 8 | Premine = 6, 9 | Cap = 8, 10 | Amount = 10, 11 | HeightStart = 12, 12 | HeightEnd = 14, 13 | OffsetStart = 16, 14 | OffsetEnd = 18, 15 | Mint = 20, 16 | Pointer = 22, 17 | #[allow(unused)] 18 | Cenotaph = 126, 19 | 20 | Divisibility = 1, 21 | Spacers = 3, 22 | Symbol = 5, 23 | #[allow(unused)] 24 | Nop = 127, 25 | 26 | Protocol = 16383, 27 | Burn = 83, 28 | Message = 81, 29 | Refund = 93, 30 | ProtoPointer = 91, 31 | From = 95, 32 | } 33 | 34 | impl Tag { 35 | pub fn take_all(self, fields: &mut BTreeMap>) -> Option> { 36 | let field = fields.get_mut(&self.into())?; 37 | 38 | let mut values: Vec = vec![]; 39 | 40 | for (_i, v) in field.iter_mut().enumerate() { 41 | values.push(v.clone()) 42 | } 43 | 44 | Some(values) 45 | } 46 | pub fn take( 47 | self, 48 | fields: &mut BTreeMap>, 49 | with: impl Fn([u128; N]) -> Option, 50 | ) -> Option { 51 | let field = fields.get_mut(&self.into())?; 52 | 53 | let mut values: [u128; N] = [0; N]; 54 | 55 | for (i, v) in values.iter_mut().enumerate() { 56 | *v = *field.get(i)?; 57 | } 58 | 59 | let value = with(values)?; 60 | 61 | field.drain(0..N); 62 | 63 | if field.is_empty() { 64 | fields.remove(&self.into()).unwrap(); 65 | } 66 | 67 | Some(value) 68 | } 69 | 70 | /// note: removed pub(super) on the following two functinos so we can use them 71 | /// inside the protorunes context 72 | pub fn encode(self, values: [u128; N], payload: &mut Vec) { 73 | for value in values { 74 | varint::encode_to_vec(self.into(), payload); 75 | varint::encode_to_vec(value, payload); 76 | } 77 | } 78 | 79 | pub fn encode_option>(self, value: Option, payload: &mut Vec) { 80 | if let Some(value) = value { 81 | self.encode([value.into()], payload) 82 | } 83 | } 84 | } 85 | 86 | impl From for u128 { 87 | fn from(tag: Tag) -> Self { 88 | tag as u128 89 | } 90 | } 91 | 92 | impl PartialEq for Tag { 93 | fn eq(&self, other: &u128) -> bool { 94 | u128::from(*self) == *other 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | #[test] 103 | fn from_u128() { 104 | assert_eq!(0u128, Tag::Body.into()); 105 | assert_eq!(2u128, Tag::Flags.into()); 106 | } 107 | 108 | #[test] 109 | fn partial_eq() { 110 | assert_eq!(Tag::Body, 0); 111 | assert_eq!(Tag::Flags, 2); 112 | } 113 | 114 | #[test] 115 | fn take() { 116 | let mut fields = vec![(2, vec![3].into_iter().collect())] 117 | .into_iter() 118 | .collect::>>(); 119 | 120 | assert_eq!(Tag::Flags.take(&mut fields, |[_]| None::), None); 121 | 122 | assert!(!fields.is_empty()); 123 | 124 | assert_eq!(Tag::Flags.take(&mut fields, |[flags]| Some(flags)), Some(3)); 125 | 126 | assert!(fields.is_empty()); 127 | 128 | assert_eq!(Tag::Flags.take(&mut fields, |[flags]| Some(flags)), None); 129 | } 130 | 131 | #[test] 132 | fn take_leaves_unconsumed_values() { 133 | let mut fields = vec![(2, vec![1, 2, 3].into_iter().collect())] 134 | .into_iter() 135 | .collect::>>(); 136 | 137 | assert_eq!(fields[&2].len(), 3); 138 | 139 | assert_eq!(Tag::Flags.take(&mut fields, |[_]| None::), None); 140 | 141 | assert_eq!(fields[&2].len(), 3); 142 | 143 | assert_eq!( 144 | Tag::Flags.take(&mut fields, |[a, b]| Some((a, b))), 145 | Some((1, 2)) 146 | ); 147 | 148 | assert_eq!(fields[&2].len(), 1); 149 | 150 | assert_eq!(Tag::Flags.take(&mut fields, |[a]| Some(a)), Some(3)); 151 | 152 | assert_eq!(fields.get(&2), None); 153 | } 154 | 155 | #[test] 156 | fn encode() { 157 | let mut payload = Vec::new(); 158 | 159 | Tag::Flags.encode([3], &mut payload); 160 | 161 | assert_eq!(payload, [2, 3]); 162 | 163 | Tag::Rune.encode([5], &mut payload); 164 | 165 | assert_eq!(payload, [2, 3, 4, 5]); 166 | 167 | Tag::Rune.encode([5, 6], &mut payload); 168 | 169 | assert_eq!(payload, [2, 3, 4, 5, 4, 5, 4, 6]); 170 | } 171 | 172 | #[test] 173 | fn burn_and_nop_are_one_byte() { 174 | let mut payload = Vec::new(); 175 | Tag::Cenotaph.encode([0], &mut payload); 176 | assert_eq!(payload.len(), 2); 177 | 178 | let mut payload = Vec::new(); 179 | Tag::Nop.encode([0], &mut payload); 180 | assert_eq!(payload.len(), 2); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /crates/ordinals/src/sat_point.rs: -------------------------------------------------------------------------------- 1 | use {super::*, bitcoin::transaction::ParseOutPointError}; 2 | 3 | /// A satpoint identifies the location of a sat in an output. 4 | /// 5 | /// The string representation of a satpoint consists of that of an outpoint, 6 | /// which identifies and output, followed by `:OFFSET`. For example, the string 7 | /// representation of the first sat of the genesis block coinbase output is 8 | /// `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0:0`, 9 | /// that of the second sat of the genesis block coinbase output is 10 | /// `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0:1`, and 11 | /// so on and so on. 12 | #[derive( 13 | Debug, 14 | PartialEq, 15 | Copy, 16 | Clone, 17 | Eq, 18 | PartialOrd, 19 | Ord, 20 | Default, 21 | Hash, 22 | DeserializeFromStr, 23 | SerializeDisplay, 24 | )] 25 | pub struct SatPoint { 26 | pub outpoint: OutPoint, 27 | pub offset: u64, 28 | } 29 | 30 | impl Display for SatPoint { 31 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 32 | write!(f, "{}:{}", self.outpoint, self.offset) 33 | } 34 | } 35 | 36 | impl Encodable for SatPoint { 37 | fn consensus_encode( 38 | &self, 39 | s: &mut S, 40 | ) -> Result { 41 | let len = self.outpoint.consensus_encode(s)?; 42 | Ok(len + self.offset.consensus_encode(s)?) 43 | } 44 | } 45 | 46 | impl Decodable for SatPoint { 47 | fn consensus_decode( 48 | d: &mut D, 49 | ) -> Result { 50 | Ok(SatPoint { 51 | outpoint: Decodable::consensus_decode(d)?, 52 | offset: Decodable::consensus_decode(d)?, 53 | }) 54 | } 55 | } 56 | 57 | impl FromStr for SatPoint { 58 | type Err = Error; 59 | 60 | fn from_str(s: &str) -> Result { 61 | let (outpoint, offset) = s.rsplit_once(':').ok_or_else(|| Error::Colon(s.into()))?; 62 | 63 | Ok(SatPoint { 64 | outpoint: outpoint 65 | .parse::() 66 | .map_err(|err| Error::Outpoint { 67 | outpoint: outpoint.into(), 68 | err, 69 | })?, 70 | offset: offset.parse::().map_err(|err| Error::Offset { 71 | offset: offset.into(), 72 | err, 73 | })?, 74 | }) 75 | } 76 | } 77 | 78 | #[derive(Debug, Error)] 79 | pub enum Error { 80 | #[error("satpoint `{0}` missing colon")] 81 | Colon(String), 82 | #[error("satpoint offset `{offset}` invalid: {err}")] 83 | Offset { offset: String, err: ParseIntError }, 84 | #[error("satpoint outpoint `{outpoint}` invalid: {err}")] 85 | Outpoint { 86 | outpoint: String, 87 | err: ParseOutPointError, 88 | }, 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::*; 94 | 95 | #[test] 96 | fn error() { 97 | assert_eq!( 98 | "foo".parse::().unwrap_err().to_string(), 99 | "satpoint `foo` missing colon" 100 | ); 101 | 102 | assert_eq!( 103 | "foo:bar".parse::().unwrap_err().to_string(), 104 | "satpoint outpoint `foo` invalid: OutPoint not in : format" 105 | ); 106 | 107 | assert_eq!( 108 | "1111111111111111111111111111111111111111111111111111111111111111:1:bar" 109 | .parse::() 110 | .unwrap_err() 111 | .to_string(), 112 | "satpoint offset `bar` invalid: invalid digit found in string" 113 | ); 114 | } 115 | 116 | #[test] 117 | fn from_str_ok() { 118 | assert_eq!( 119 | "1111111111111111111111111111111111111111111111111111111111111111:1:1" 120 | .parse::() 121 | .unwrap(), 122 | SatPoint { 123 | outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1" 124 | .parse() 125 | .unwrap(), 126 | offset: 1, 127 | } 128 | ); 129 | } 130 | 131 | #[test] 132 | fn from_str_err() { 133 | "abc".parse::().unwrap_err(); 134 | 135 | "abc:xyz".parse::().unwrap_err(); 136 | 137 | "1111111111111111111111111111111111111111111111111111111111111111:1" 138 | .parse::() 139 | .unwrap_err(); 140 | 141 | "1111111111111111111111111111111111111111111111111111111111111111:1:foo" 142 | .parse::() 143 | .unwrap_err(); 144 | } 145 | 146 | #[test] 147 | fn deserialize_ok() { 148 | assert_eq!( 149 | serde_json::from_str::( 150 | "\"1111111111111111111111111111111111111111111111111111111111111111:1:1\"" 151 | ) 152 | .unwrap(), 153 | SatPoint { 154 | outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1" 155 | .parse() 156 | .unwrap(), 157 | offset: 1, 158 | } 159 | ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /crates/ordinals/src/spaced_rune.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive( 4 | Copy, 5 | Clone, 6 | Debug, 7 | PartialEq, 8 | Ord, 9 | PartialOrd, 10 | Eq, 11 | Default, 12 | DeserializeFromStr, 13 | SerializeDisplay, 14 | )] 15 | pub struct SpacedRune { 16 | pub rune: Rune, 17 | pub spacers: u32, 18 | } 19 | 20 | impl SpacedRune { 21 | pub fn new(rune: Rune, spacers: u32) -> Self { 22 | Self { rune, spacers } 23 | } 24 | } 25 | 26 | impl FromStr for SpacedRune { 27 | type Err = Error; 28 | 29 | fn from_str(s: &str) -> Result { 30 | let mut rune = String::new(); 31 | let mut spacers = 0u32; 32 | 33 | for c in s.chars() { 34 | match c { 35 | 'A'..='Z' => rune.push(c), 36 | '.' | '•' => { 37 | let flag = 1 << rune.len().checked_sub(1).ok_or(Error::LeadingSpacer)?; 38 | if spacers & flag != 0 { 39 | return Err(Error::DoubleSpacer); 40 | } 41 | spacers |= flag; 42 | } 43 | _ => return Err(Error::Character(c)), 44 | } 45 | } 46 | 47 | if 32 - spacers.leading_zeros() >= rune.len().try_into().unwrap() { 48 | return Err(Error::TrailingSpacer); 49 | } 50 | 51 | Ok(SpacedRune { 52 | rune: rune.parse().map_err(Error::Rune)?, 53 | spacers, 54 | }) 55 | } 56 | } 57 | 58 | impl Display for SpacedRune { 59 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 60 | let rune = self.rune.to_string(); 61 | 62 | for (i, c) in rune.chars().enumerate() { 63 | write!(f, "{c}")?; 64 | 65 | if i < rune.len() - 1 && self.spacers & 1 << i != 0 { 66 | write!(f, "•")?; 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | } 73 | 74 | #[derive(Debug, PartialEq)] 75 | pub enum Error { 76 | LeadingSpacer, 77 | TrailingSpacer, 78 | DoubleSpacer, 79 | Character(char), 80 | Rune(rune::Error), 81 | } 82 | 83 | impl Display for Error { 84 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 85 | match self { 86 | Self::Character(c) => write!(f, "invalid character `{c}`"), 87 | Self::DoubleSpacer => write!(f, "double spacer"), 88 | Self::LeadingSpacer => write!(f, "leading spacer"), 89 | Self::TrailingSpacer => write!(f, "trailing spacer"), 90 | Self::Rune(err) => write!(f, "{err}"), 91 | } 92 | } 93 | } 94 | 95 | impl std::error::Error for Error {} 96 | 97 | #[cfg(test)] 98 | mod tests { 99 | use super::*; 100 | 101 | #[test] 102 | fn display() { 103 | assert_eq!("A.B".parse::().unwrap().to_string(), "A•B"); 104 | assert_eq!("A.B.C".parse::().unwrap().to_string(), "A•B•C"); 105 | assert_eq!( 106 | SpacedRune { 107 | rune: Rune(0), 108 | spacers: 1 109 | } 110 | .to_string(), 111 | "A" 112 | ); 113 | } 114 | 115 | #[test] 116 | fn from_str() { 117 | #[track_caller] 118 | fn case(s: &str, rune: &str, spacers: u32) { 119 | assert_eq!( 120 | s.parse::().unwrap(), 121 | SpacedRune { 122 | rune: rune.parse().unwrap(), 123 | spacers 124 | }, 125 | ); 126 | } 127 | 128 | assert_eq!( 129 | ".A".parse::().unwrap_err(), 130 | Error::LeadingSpacer, 131 | ); 132 | 133 | assert_eq!( 134 | "A..B".parse::().unwrap_err(), 135 | Error::DoubleSpacer, 136 | ); 137 | 138 | assert_eq!( 139 | "A.".parse::().unwrap_err(), 140 | Error::TrailingSpacer, 141 | ); 142 | 143 | assert_eq!( 144 | "Ax".parse::().unwrap_err(), 145 | Error::Character('x') 146 | ); 147 | 148 | case("A.B", "AB", 0b1); 149 | case("A.B.C", "ABC", 0b11); 150 | case("A•B", "AB", 0b1); 151 | case("A•B•C", "ABC", 0b11); 152 | case("A•BC", "ABC", 0b1); 153 | } 154 | 155 | #[test] 156 | fn serde() { 157 | let spaced_rune = SpacedRune { 158 | rune: Rune(26), 159 | spacers: 1, 160 | }; 161 | let json = "\"A•A\""; 162 | assert_eq!(serde_json::to_string(&spaced_rune).unwrap(), json); 163 | assert_eq!( 164 | serde_json::from_str::(json).unwrap(), 165 | spaced_rune 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /crates/ordinals/src/terms.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq)] 4 | pub struct Terms { 5 | pub amount: Option, 6 | pub cap: Option, 7 | pub height: (Option, Option), 8 | pub offset: (Option, Option), 9 | } 10 | -------------------------------------------------------------------------------- /crates/ordinals/src/varint.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn encode_to_vec(mut n: u128, v: &mut Vec) { 4 | while n >> 7 > 0 { 5 | v.push(n.to_le_bytes()[0] | 0b1000_0000); 6 | n >>= 7; 7 | } 8 | v.push(n.to_le_bytes()[0]); 9 | } 10 | 11 | pub fn decode(buffer: &[u8]) -> Result<(u128, usize), Error> { 12 | let mut n = 0u128; 13 | 14 | for (i, &byte) in buffer.iter().enumerate() { 15 | if i > 18 { 16 | return Err(Error::Overlong); 17 | } 18 | 19 | let value = u128::from(byte) & 0b0111_1111; 20 | 21 | if i == 18 && value & 0b0111_1100 != 0 { 22 | return Err(Error::Overflow); 23 | } 24 | 25 | n |= value << (7 * i); 26 | 27 | if byte & 0b1000_0000 == 0 { 28 | return Ok((n, i + 1)); 29 | } 30 | } 31 | 32 | Err(Error::Unterminated) 33 | } 34 | 35 | pub fn encode(n: u128) -> Vec { 36 | let mut v = Vec::new(); 37 | encode_to_vec(n, &mut v); 38 | v 39 | } 40 | 41 | #[derive(PartialEq, Debug)] 42 | pub enum Error { 43 | Overlong, 44 | Overflow, 45 | Unterminated, 46 | } 47 | 48 | impl Display for Error { 49 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 50 | match self { 51 | Self::Overlong => write!(f, "too long"), 52 | Self::Overflow => write!(f, "overflow"), 53 | Self::Unterminated => write!(f, "unterminated"), 54 | } 55 | } 56 | } 57 | 58 | impl std::error::Error for Error {} 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn zero_round_trips_successfully() { 66 | let n = 0; 67 | let encoded = encode(n); 68 | let (decoded, length) = decode(&encoded).unwrap(); 69 | assert_eq!(decoded, n); 70 | assert_eq!(length, encoded.len()); 71 | } 72 | 73 | #[test] 74 | fn u128_max_round_trips_successfully() { 75 | let n = u128::MAX; 76 | let encoded = encode(n); 77 | let (decoded, length) = decode(&encoded).unwrap(); 78 | assert_eq!(decoded, n); 79 | assert_eq!(length, encoded.len()); 80 | } 81 | 82 | #[test] 83 | fn powers_of_two_round_trip_successfully() { 84 | for i in 0..128 { 85 | let n = 1 << i; 86 | let encoded = encode(n); 87 | let (decoded, length) = decode(&encoded).unwrap(); 88 | assert_eq!(decoded, n); 89 | assert_eq!(length, encoded.len()); 90 | } 91 | } 92 | 93 | #[test] 94 | fn alternating_bit_strings_round_trip_successfully() { 95 | let mut n = 0; 96 | 97 | for i in 0..129 { 98 | n = n << 1 | (i % 2); 99 | let encoded = encode(n); 100 | let (decoded, length) = decode(&encoded).unwrap(); 101 | assert_eq!(decoded, n); 102 | assert_eq!(length, encoded.len()); 103 | } 104 | } 105 | 106 | #[test] 107 | fn varints_may_not_be_longer_than_19_bytes() { 108 | const VALID: [u8; 19] = [ 109 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 110 | 128, 0, 111 | ]; 112 | 113 | const INVALID: [u8; 20] = [ 114 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 115 | 128, 128, 0, 116 | ]; 117 | 118 | assert_eq!(decode(&VALID), Ok((0, 19))); 119 | assert_eq!(decode(&INVALID), Err(Error::Overlong)); 120 | } 121 | 122 | #[test] 123 | fn varints_may_not_overflow_u128() { 124 | assert_eq!( 125 | decode(&[ 126 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127 | 128, 128, 64, 128 | ]), 129 | Err(Error::Overflow) 130 | ); 131 | assert_eq!( 132 | decode(&[ 133 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 134 | 128, 128, 32, 135 | ]), 136 | Err(Error::Overflow) 137 | ); 138 | assert_eq!( 139 | decode(&[ 140 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 141 | 128, 128, 16, 142 | ]), 143 | Err(Error::Overflow) 144 | ); 145 | assert_eq!( 146 | decode(&[ 147 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 148 | 128, 128, 8, 149 | ]), 150 | Err(Error::Overflow) 151 | ); 152 | assert_eq!( 153 | decode(&[ 154 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 155 | 128, 128, 4, 156 | ]), 157 | Err(Error::Overflow) 158 | ); 159 | assert_eq!( 160 | decode(&[ 161 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 162 | 128, 128, 2, 163 | ]), 164 | Ok((2u128.pow(127), 19)) 165 | ); 166 | } 167 | 168 | #[test] 169 | fn varints_must_be_terminated() { 170 | assert_eq!(decode(&[128]), Err(Error::Unterminated)); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /crates/protorune-support/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protorune-support" 3 | version = "0.2.3" 4 | edition = "2021" 5 | description = "protorunes support libs for Rust sources" 6 | license = "MIT" 7 | repository = "https://github.com/kungfuflex/protorune-rs" 8 | resolver = "2" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [dependencies] 14 | bitcoin = { version = "0.32.4", features = ["rand"] } 15 | serde = "1.0.210" 16 | ordinals = { path = "../ordinals" } 17 | anyhow = "1.0.90" 18 | metashrew-support = { workspace = true } 19 | hex = "0.4.3" 20 | protobuf = "3.6.0" 21 | bech32 = "0.11.0" 22 | 23 | [build-dependencies] 24 | protobuf-codegen = "3.4.0" 25 | protoc-rust = { version = "2.28.0" } 26 | protoc-bin-vendored = "3.0.0" 27 | -------------------------------------------------------------------------------- /crates/protorune-support/build.rs: -------------------------------------------------------------------------------- 1 | use protobuf_codegen; 2 | use protoc_bin_vendored; 3 | fn main() { 4 | protobuf_codegen::Codegen::new() 5 | .protoc() 6 | .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) 7 | .out_dir("src/proto") 8 | .inputs(&["proto/protorune.proto"]) 9 | .include("proto") 10 | .run() 11 | .expect("running protoc failed"); 12 | } 13 | -------------------------------------------------------------------------------- /crates/protorune-support/proto/protorune.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package protorune; 4 | 5 | message RuneId { 6 | uint32 height = 1; 7 | uint32 txindex = 2; 8 | } 9 | 10 | message ProtoruneRuneId { 11 | uint128 height = 1; 12 | uint128 txindex = 2; 13 | } 14 | 15 | message Rune { 16 | ProtoruneRuneId runeId = 1; 17 | string name = 2; 18 | uint32 divisibility = 3; 19 | uint32 spacers = 4; 20 | string symbol = 5; 21 | } 22 | 23 | message BalanceSheetItem { 24 | Rune rune = 1; 25 | uint128 balance = 2; 26 | } 27 | 28 | message BalanceSheet { 29 | repeated BalanceSheetItem entries = 1; 30 | } 31 | 32 | message Outpoint { 33 | bytes txid = 1; 34 | uint32 vout = 2; 35 | } 36 | 37 | message OutpointWithProtocol { 38 | bytes txid = 1; 39 | uint32 vout = 2; 40 | uint128 protocol = 3; 41 | } 42 | message Output { 43 | bytes script = 1; 44 | uint64 value = 2; 45 | } 46 | 47 | message OutpointResponse { 48 | BalanceSheet balances = 1; 49 | Outpoint outpoint = 2; 50 | Output output = 3; 51 | uint32 height = 4; 52 | uint32 txindex = 5; 53 | } 54 | 55 | message PaginationInput { 56 | uint32 start = 1; 57 | uint32 end = 2; 58 | } 59 | 60 | message WalletRequest { 61 | bytes wallet = 1; 62 | } 63 | 64 | message WalletResponse { 65 | repeated OutpointResponse outpoints = 1; 66 | BalanceSheet balances = 2; 67 | } 68 | 69 | message ProtorunesWalletRequest { 70 | bytes wallet = 1; 71 | uint128 protocol_tag = 2; 72 | } 73 | 74 | message RunesByHeightRequest { 75 | uint64 height = 1; 76 | } 77 | 78 | message ProtorunesByHeightRequest { 79 | uint64 height = 1; 80 | uint128 protocol_tag = 2; 81 | } 82 | 83 | message RunesResponse { 84 | repeated Rune runes = 1; 85 | } 86 | message ProtoBurn { 87 | uint128 protocol_tag = 1; 88 | uint32 pointer = 2; 89 | } 90 | 91 | message uint128 { 92 | uint64 lo = 1; 93 | uint64 hi = 2; 94 | } 95 | 96 | message Clause { 97 | ProtoruneRuneId rune = 1; 98 | uint128 amount = 2; 99 | } 100 | 101 | message Predicate { 102 | repeated Clause clauses = 1; 103 | } 104 | 105 | message ProtoMessage { 106 | bytes calldata = 1; 107 | Predicate predicate = 2; 108 | uint32 pointer = 3; 109 | uint32 refund_pointer = 4; 110 | } 111 | 112 | message RuntimeInput { 113 | uint128 protocol_tag = 1; 114 | } 115 | 116 | message Runtime { 117 | BalanceSheet balances = 1; 118 | } 119 | -------------------------------------------------------------------------------- /crates/protorune-support/src/byte_utils.rs: -------------------------------------------------------------------------------- 1 | pub trait ByteUtils { 2 | fn to_aligned_bytes(self) -> Vec; 3 | fn snap_to_15_bytes(self) -> Vec; 4 | fn to_u32(self) -> u32; 5 | } 6 | 7 | impl ByteUtils for u128 { 8 | // Removes the leading bytes in the u128 (little endian) 9 | // Note that this would remove the last byte of LEB encoded protostones generated under the assumption that it is only using the first 15 bytes 10 | // also opens up the possibility of potentially using the last 6 bits of the u128 for protostones encoding since at this step it would not have 11 | // been labeled as a cenotaph for exceeding the max size of a runestone varint. 12 | fn to_aligned_bytes(self) -> Vec { 13 | let mut ar: Vec = (self.to_le_bytes()).try_into().unwrap(); 14 | while let Some(&last) = ar.last() { 15 | if last != 0 { 16 | break; // Stop if we encounter a non-zero byte 17 | } 18 | ar.pop(); // Remove the last element if it's zero 19 | } 20 | ar 21 | } 22 | 23 | // uint128s -> leb128 max needs 19 bytes, since 128/7 = 18.3, so an extra byte is needed to store the last two bits in the uint128. 24 | // Runes will produce cenotaph if it needs to process more than 18 bytes for any leb128, so we cannot use the upper two bits in a uint128 25 | // Simplest solution is to not use the upper 8 bits (upper byte) of the uint128 so the upper 2 bits can never be set. 26 | // Downside is we miss out on 6 bits of storage before we have to push another tag 27 | fn snap_to_15_bytes(self) -> Vec { 28 | let mut ar: Vec = (self.to_le_bytes()).try_into().unwrap(); 29 | ar.pop(); 30 | ar 31 | } 32 | 33 | fn to_u32(self) -> u32 { 34 | self as u32 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/protorune-support/src/constants.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::Network::Bitcoin; 2 | 3 | pub const RUNESTONE_TAG: u16 = 0x5d6a; 4 | pub const OP_RETURN: u8 = 0x6a; 5 | pub const GENESIS: u32 = 840000; 6 | pub const NETWORK: bitcoin::Network = Bitcoin; 7 | pub const MINIMUM_NAME: u128 = 99246114928149462; 8 | pub const RESERVED_NAME: u128 = 6402364363415443603228541259936211926; 9 | pub const TWENTY_SIX: u128 = 26; 10 | 11 | pub const SUBSIDY_HALVING_INTERVAL: u64 = 210_000; 12 | pub const HEIGHT_INTERVAL: u64 = 17_500; 13 | 14 | pub const MAX_BYTES_LEB128_INT: usize = 18; 15 | -------------------------------------------------------------------------------- /crates/protorune-support/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod balance_sheet; 2 | pub mod byte_utils; 3 | pub mod constants; 4 | pub mod network; 5 | pub mod proto; 6 | pub mod protostone; 7 | pub mod rune_transfer; 8 | pub mod utils; 9 | 10 | use anyhow; 11 | use bitcoin::hashes::Hash; 12 | use bitcoin::{OutPoint, Txid}; 13 | 14 | impl TryInto for proto::protorune::Outpoint { 15 | type Error = anyhow::Error; 16 | fn try_into(self) -> Result { 17 | Ok(OutPoint { 18 | txid: Txid::from_byte_array(<&[u8] as TryInto<[u8; 32]>>::try_into(&self.txid)?), 19 | vout: self.vout.into(), 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/protorune-support/src/network.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bech32::Hrp; 3 | use bitcoin::Script; 4 | use metashrew_support::address::{AddressEncoding, Payload}; 5 | static mut _NETWORK: Option = None; 6 | 7 | #[derive(Clone, Debug, Default)] 8 | pub struct NetworkParams { 9 | pub bech32_prefix: String, 10 | pub p2pkh_prefix: u8, 11 | pub p2sh_prefix: u8, 12 | } 13 | 14 | #[allow(static_mut_refs)] 15 | pub fn set_network(params: NetworkParams) { 16 | unsafe { 17 | _NETWORK = Some(params); 18 | } 19 | } 20 | 21 | #[allow(static_mut_refs)] 22 | pub fn get_network() -> &'static NetworkParams { 23 | unsafe { _NETWORK.as_ref().unwrap() } 24 | } 25 | 26 | #[allow(static_mut_refs)] 27 | pub fn get_network_option() -> Option<&'static NetworkParams> { 28 | unsafe { _NETWORK.as_ref().clone() } 29 | } 30 | 31 | pub fn to_address_str(script: &Script) -> Result { 32 | let config = get_network(); 33 | Ok(AddressEncoding { 34 | p2pkh_prefix: config.p2pkh_prefix, 35 | p2sh_prefix: config.p2sh_prefix, 36 | hrp: Hrp::parse_unchecked(&config.bech32_prefix), 37 | payload: &Payload::from_script(script)?, 38 | } 39 | .to_string()) 40 | } 41 | -------------------------------------------------------------------------------- /crates/protorune-support/src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | 3 | pub mod protorune; 4 | -------------------------------------------------------------------------------- /crates/protorune-support/src/rune_transfer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use metashrew_support::index_pointer::KeyValuePointer; 4 | 5 | use crate::balance_sheet::{BalanceSheet, BalanceSheetOperations, ProtoruneRuneId}; 6 | use anyhow::{anyhow, Result}; 7 | 8 | #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 9 | pub struct RuneTransfer { 10 | pub id: ProtoruneRuneId, 11 | pub value: u128, 12 | } 13 | 14 | impl RuneTransfer { 15 | pub fn from_balance_sheet(s: BalanceSheet

) -> Vec { 16 | s.balances() 17 | .iter() 18 | .filter_map(|(id, v)| { 19 | if *v > 0 { 20 | Some(RuneTransfer { id: *id, value: *v }) 21 | } else { 22 | None 23 | } 24 | }) 25 | .collect::>() 26 | } 27 | } 28 | 29 | /// Parameters: 30 | /// balances_by_output: The running store of balances by each transaction output for 31 | /// the current transaction being handled. 32 | /// sheet: The balance sheet to increase the balances by 33 | /// vout: The target transaction vout to receive the runes 34 | pub fn increase_balances_using_sheet( 35 | balances_by_output: &mut BTreeMap>, 36 | sheet: &BalanceSheet

, 37 | vout: u32, 38 | ) -> Result<()> { 39 | if !balances_by_output.contains_key(&vout) { 40 | balances_by_output.insert(vout, BalanceSheet::default()); 41 | } 42 | sheet.pipe(balances_by_output.get_mut(&vout).unwrap())?; 43 | Ok(()) 44 | } 45 | 46 | /// Refunds all input runes to the refund pointer 47 | pub fn refund_to_refund_pointer( 48 | balances_by_output: &mut BTreeMap>, 49 | protomessage_vout: u32, 50 | refund_pointer: u32, 51 | ) -> Result<()> { 52 | // grab the balance of the protomessage vout 53 | let sheet = balances_by_output 54 | .get(&protomessage_vout) 55 | .map(|v| v.clone()) 56 | .unwrap_or_else(|| BalanceSheet::default()); 57 | // we want to remove any balance from the protomessage vout 58 | balances_by_output.remove(&protomessage_vout); 59 | increase_balances_using_sheet(balances_by_output, &sheet, refund_pointer) 60 | } 61 | -------------------------------------------------------------------------------- /crates/protorune-support/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bitcoin::consensus::{ 3 | deserialize_partial, 4 | encode::{Decodable, Encodable}, 5 | }; 6 | use bitcoin::hashes::Hash; 7 | use bitcoin::{OutPoint, Txid}; 8 | use metashrew_support::utils::{is_empty, remaining_slice}; 9 | use ordinals::varint; 10 | use std::{io::BufRead, string}; 11 | pub fn consensus_encode(v: &T) -> Result> { 12 | let mut result = Vec::::new(); 13 | ::consensus_encode::>(v, &mut result)?; 14 | Ok(result) 15 | } 16 | 17 | pub fn consensus_decode(cursor: &mut std::io::Cursor>) -> Result { 18 | let slice = &cursor.get_ref()[cursor.position() as usize..cursor.get_ref().len() as usize]; 19 | let deserialized: (T, usize) = deserialize_partial(slice)?; 20 | cursor.consume(deserialized.1); 21 | Ok(deserialized.0) 22 | } 23 | 24 | pub fn tx_hex_to_txid(s: &str) -> Result { 25 | Ok(Txid::from_byte_array( 26 | as AsRef<[u8]>>::as_ref( 27 | &hex::decode(s)?.iter().cloned().rev().collect::>(), 28 | ) 29 | .try_into()?, 30 | )) 31 | } 32 | 33 | pub fn reverse_txid(v: &Txid) -> Txid { 34 | let reversed_bytes: Vec = v 35 | .clone() 36 | .as_byte_array() 37 | .into_iter() 38 | .map(|v| v.clone()) 39 | .rev() 40 | .collect::>(); 41 | let reversed_bytes_ref: &[u8] = &reversed_bytes; 42 | Txid::from_byte_array(reversed_bytes_ref.try_into().unwrap()) 43 | } 44 | 45 | pub fn outpoint_encode(v: &OutPoint) -> Result> { 46 | consensus_encode(&v) 47 | } 48 | 49 | pub fn decode_varint_list(cursor: &mut std::io::Cursor>) -> Result> { 50 | let mut result: Vec = vec![]; 51 | while !is_empty(cursor) { 52 | let (n, sz) = varint::decode(remaining_slice(cursor))?; 53 | cursor.consume(sz); 54 | result.push(n); 55 | } 56 | Ok(result) 57 | } 58 | 59 | /// returns the values in a LEB encoded stream 60 | pub fn encode_varint_list(values: &Vec) -> Vec { 61 | let mut result = Vec::::new(); 62 | for value in values { 63 | varint::encode_to_vec(*value, &mut result); 64 | } 65 | result 66 | } 67 | 68 | pub fn field_to_name(data: &u128) -> String { 69 | let mut v = data + 1; // Increment by 1 70 | let mut result = String::new(); 71 | let twenty_six: u128 = 26; 72 | 73 | while v > 0 { 74 | let mut y = (v % twenty_six) as u32; 75 | if y == 0 { 76 | y = 26; 77 | } 78 | 79 | // Convert number to character (A-Z, where A is 65 in ASCII) 80 | result.insert(0, char::from_u32(64 + y).unwrap()); 81 | 82 | v -= 1; // Decrement v by 1 83 | v /= twenty_six; // Divide v by 26 for next iteration 84 | } 85 | 86 | result 87 | } 88 | 89 | pub fn get_network() -> bitcoin::Network { 90 | let network = std::env::var("network").unwrap_or_else(|_| { 91 | eprintln!("Environment variable 'network' is not set. Defaulting to 'bitcoin'."); 92 | "bitcoin".to_string() 93 | }); 94 | 95 | match network.as_str() { 96 | "bitcoin" => bitcoin::Network::Bitcoin, 97 | "testnet" => bitcoin::Network::Testnet, 98 | "regtest" => bitcoin::Network::Regtest, 99 | "signet" => bitcoin::Network::Signet, 100 | _ => { 101 | eprintln!("Invalid network '{}'. Defaulting to 'bitcoin'.", network); 102 | bitcoin::Network::Bitcoin 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/protorune/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | 4 | [target.wasm32-unknown-unknown] 5 | runner = "wasm-bindgen-test-runner" 6 | -------------------------------------------------------------------------------- /crates/protorune/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.swp 3 | *.swo 4 | -------------------------------------------------------------------------------- /crates/protorune/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protorune" 3 | version = "0.2.3" 4 | edition = "2021" 5 | description = "protorunes support libs for Rust sources" 6 | license = "MIT" 7 | repository = "https://github.com/kungfuflex/protorune-rs" 8 | resolver = "2" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [features] 14 | test-utils = [] 15 | mainnet = [] 16 | cache = [] 17 | 18 | [dependencies] 19 | anyhow = "1.0.89" 20 | bitcoin = { version = "0.32.4", features = ["rand"] } 21 | metashrew-core = { workspace = true } 22 | metashrew-support = { workspace = true } 23 | protorune-support = { path = "../protorune-support" } 24 | ordinals = { path = "../ordinals" } 25 | ruint = { workspace = true } 26 | wasm-bindgen = { workspace = true } 27 | byteorder = { workspace = true } 28 | wasm-bindgen-test = { workspace = true } 29 | once_cell = "1.20.1" 30 | serde = { version = "1.0.210", features = ["derive"] } 31 | serde_json = { workspace = true } 32 | hex = { workspace = true } 33 | protobuf = { workspace = true } 34 | 35 | [dev-dependencies] 36 | metashrew-core = { git = "https://github.com/sandshrewmetaprotocols/metashrew", features = ["test-utils"] } 37 | wasm-bindgen-cli = "0.2.99" 38 | getrandom = { version = "0.2.15", features = ["js"] } 39 | protorune = { path = ".", features = ["test-utils"] } 40 | -------------------------------------------------------------------------------- /crates/protorune/README.md: -------------------------------------------------------------------------------- 1 | # protorune-rs 2 | 3 | Rust abstractions for protorunes subprotocols, built for a metashrew runtime. 4 | 5 | # Some helpful commands 6 | 7 | Building 8 | 9 | ``` 10 | cargo build 11 | ``` 12 | 13 | Integration Testing (end to end) 14 | 15 | - These test the compiled wasm and generally have test fixtures that create runes, do protoburns, and test other functionality 16 | 17 | ``` 18 | cargo test 19 | ``` 20 | 21 | Unit testing 22 | 23 | - These are written inside the library rust code 24 | - Do not compile to wasm, instead unit test the native rust. Therefore, you need to find the correct target for your local machine to properly run these tests. Below are some common targets for some architectures: 25 | - Macbook intel x86: `x86_64-apple-darwin` 26 | - Macbook Apple silicon: `aarch64-apple-darwin` 27 | 28 | ``` 29 | cargo test -p ordinals --target TARGET # to test the ordinals crate, which has some protostones unit tests 30 | 31 | cargo test --target TARGET 32 | ``` 33 | 34 | ## License 35 | 36 | MIT 37 | -------------------------------------------------------------------------------- /crates/protorune/src/message.rs: -------------------------------------------------------------------------------- 1 | use crate::tables::RuneTable; 2 | use anyhow::Result; 3 | use bitcoin::{Block, OutPoint, Transaction}; 4 | use metashrew_core::index_pointer::AtomicPointer; 5 | use metashrew_support::index_pointer::KeyValuePointer; 6 | use protorune_support::balance_sheet::{BalanceSheet, ProtoruneRuneId}; 7 | use protorune_support::rune_transfer::RuneTransfer; 8 | use protorune_support::utils::consensus_encode; 9 | use std::u128; 10 | 11 | pub trait MessageContext { 12 | fn handle( 13 | parcel: &MessageContextParcel, 14 | ) -> Result<(Vec, BalanceSheet)>; 15 | fn protocol_tag() -> u128; 16 | fn asset_protoburned_in_protocol(id: ProtoruneRuneId) -> bool { 17 | let table = RuneTable::for_protocol(Self::protocol_tag()); 18 | if table.RUNE_ID_TO_ETCHING.select(&id.into()).get().len() > 0 { 19 | return true; 20 | } 21 | false 22 | } 23 | } 24 | 25 | #[derive(Clone, Debug)] 26 | pub struct MessageContextParcel { 27 | pub atomic: AtomicPointer, 28 | pub runes: Vec, 29 | pub transaction: Transaction, 30 | pub block: Block, 31 | pub height: u64, 32 | pub pointer: u32, 33 | pub refund_pointer: u32, 34 | pub calldata: Vec, 35 | pub sheets: Box>, 36 | pub txindex: u32, 37 | pub vout: u32, 38 | pub runtime_balances: Box>, 39 | } 40 | 41 | pub trait ToBytes { 42 | fn try_to_bytes(&self) -> Result>; 43 | } 44 | 45 | impl ToBytes for OutPoint { 46 | fn try_to_bytes(&self) -> Result> { 47 | Ok(consensus_encode(self)?) 48 | } 49 | } 50 | 51 | impl Default for MessageContextParcel { 52 | fn default() -> MessageContextParcel { 53 | let block = bitcoin::constants::genesis_block(bitcoin::Network::Bitcoin); 54 | MessageContextParcel { 55 | atomic: AtomicPointer::default(), 56 | runes: Vec::::default(), 57 | transaction: block.txdata[0].clone(), 58 | block: block.clone(), 59 | height: 0, 60 | pointer: 0, 61 | vout: 0, 62 | refund_pointer: 0, 63 | calldata: Vec::::default(), 64 | txindex: 0, 65 | runtime_balances: Box::new(BalanceSheet::default()), 66 | sheets: Box::new(BalanceSheet::default()), 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /crates/protorune/src/protorune_init.rs: -------------------------------------------------------------------------------- 1 | use crate::message::MessageContext; 2 | use crate::tables::RuneTable; 3 | use metashrew_core::index_pointer::AtomicPointer; 4 | #[allow(unused_imports)] 5 | use metashrew_core::{ 6 | flush, input, println, 7 | stdio::{stdout, Write}, 8 | }; 9 | use metashrew_support::index_pointer::KeyValuePointer; 10 | use protorune_support::balance_sheet::ProtoruneRuneId; 11 | use std::sync::Arc; 12 | 13 | pub fn index_unique_protorunes( 14 | atomic: &mut AtomicPointer, 15 | height: u64, 16 | assets: Vec, 17 | ) { 18 | let rune_table = RuneTable::for_protocol(T::protocol_tag()); 19 | let table = atomic.derive(&rune_table.HEIGHT_TO_RUNE_ID); 20 | let seen_table = atomic.derive(&rune_table.RUNE_ID_TO_INITIALIZED); 21 | assets 22 | .into_iter() 23 | .map(|v| -> Vec { v.into() }) 24 | .for_each(|v| { 25 | if seen_table.select(&v).get().as_ref().len() == 0 { 26 | seen_table.select(&v).set(Arc::new(vec![0x01])); 27 | table.select_value::(height).append(Arc::new(v)); 28 | } 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /crates/protorune/src/tests/index_protorunes_by_address.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::message::{MessageContext, MessageContextParcel}; 4 | use crate::test_helpers::{self as helpers}; 5 | use crate::{view, Protorune}; 6 | use anyhow::Result; 7 | use bitcoin::OutPoint; 8 | use metashrew_core::index_pointer::AtomicPointer; 9 | use metashrew_core::{ 10 | println, 11 | stdio::{stdout, Write}, 12 | }; 13 | use protobuf::{Message, MessageField}; 14 | use protorune_support::balance_sheet::BalanceSheet; 15 | use protorune_support::proto::protorune::{ProtorunesWalletRequest, WalletResponse}; 16 | use protorune_support::rune_transfer::RuneTransfer; 17 | use std::str::FromStr; 18 | use wasm_bindgen_test::*; 19 | 20 | use helpers::clear; 21 | 22 | // Define a NoopMessageContext that doesn't do anything special with the protorunes 23 | struct NoopMessageContext; 24 | 25 | impl MessageContext for NoopMessageContext { 26 | fn protocol_tag() -> u128 { 27 | 122 // Using the same protocol tag as in the tests 28 | } 29 | 30 | fn handle( 31 | parcel: &MessageContextParcel, 32 | ) -> Result<(Vec, BalanceSheet)> { 33 | // Just return the runes as-is without any special handling 34 | let runes: Vec = parcel.runes.clone(); 35 | Ok((runes, BalanceSheet::default())) 36 | } 37 | } 38 | 39 | // Helper function to create a transaction with OP_RETURN at the end 40 | fn create_tx_with_end_op_return(protocol_id: u128) -> bitcoin::Transaction { 41 | let first_mock_output = OutPoint { 42 | txid: bitcoin::Txid::from_str( 43 | "0000000000000000000000000000000000000000000000000000000000000000", 44 | ) 45 | .unwrap(), 46 | vout: 0, 47 | }; 48 | 49 | // Create a protoburn transaction that sends protorunes to ADDRESS1 50 | let protoburn_tx = 51 | helpers::create_default_protoburn_transaction(first_mock_output, protocol_id); 52 | 53 | protoburn_tx 54 | } 55 | 56 | // Helper function to create a block with a transaction that has OP_RETURN at the end 57 | fn create_block_with_end_op_return(protocol_id: u128) -> bitcoin::Block { 58 | let tx = create_tx_with_end_op_return(protocol_id); 59 | helpers::create_block_with_txs(vec![tx]) 60 | } 61 | 62 | // Test that protorunes_by_address returns all protorunes for a given address 63 | // #[wasm_bindgen_test] 64 | // fn test_protorunes_by_address() -> Result<()> { 65 | // clear(); 66 | // let block_height = 840000; 67 | // let protocol_id = 122; 68 | 69 | // // Create and index a block with a transaction that has OP_RETURN at the end 70 | // let test_block = create_block_with_end_op_return(protocol_id); 71 | // assert!( 72 | // Protorune::index_block::(test_block.clone(), block_height).is_ok() 73 | // ); 74 | 75 | // // Get the address from the transaction 76 | // let tx = &test_block.txdata[0]; 77 | // let address = helpers::get_address(&helpers::ADDRESS1().as_str()); 78 | // let address_bytes = address.to_string().into_bytes(); 79 | 80 | // // Create a request to get protorunes for the address 81 | // let mut request = ProtorunesWalletRequest::new(); 82 | // request.wallet = address_bytes.clone(); 83 | // request.protocol_tag = MessageField::some(protocol_id.into()); 84 | 85 | // // Call protorunes_by_address 86 | // let response: WalletResponse = 87 | // view::protorunes_by_address2(&request.write_to_bytes().unwrap())?; 88 | 89 | // println!("Response outpoints count: {}", response.outpoints.len()); 90 | 91 | // // If there are outpoints, print some information about them 92 | 93 | // assert!( 94 | // response.outpoints.len() > 0, 95 | // "must return at least one outpoint" 96 | // ); 97 | 98 | // Ok(()) 99 | // } 100 | } 101 | -------------------------------------------------------------------------------- /crates/protorune/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod index_op_return_position; 2 | pub mod index_pointer_ll; 3 | pub mod index_protoburns; 4 | pub mod index_protomessage; 5 | pub mod index_protorunes_by_address; 6 | pub mod index_runes; 7 | pub mod index_runes_edicts; 8 | pub mod index_runes_mint; 9 | #[cfg(test)] 10 | // pub mod multi_protocol; 11 | pub mod test_cenotaphs; 12 | -------------------------------------------------------------------------------- /memory-bank/productContext.md: -------------------------------------------------------------------------------- 1 | # ALKANES-RS Product Context 2 | 3 | ## Purpose and Problem Statement 4 | 5 | ALKANES-RS is designed to address the need for decentralized finance (DeFi) capabilities on the Bitcoin blockchain. While Bitcoin has strong security and widespread adoption, it lacks the native smart contract functionality that has enabled complex financial applications on other blockchains like Ethereum. 6 | 7 | The key problems ALKANES-RS solves include: 8 | 9 | 1. **Limited Programmability**: Bitcoin's scripting language is intentionally limited, making complex financial applications difficult to implement natively. 10 | 2. **DeFi Gap**: Bitcoin has lacked the rich DeFi ecosystem that exists on other blockchains, despite having the largest market capitalization. 11 | 3. **Protocol Compatibility**: Implementing DeFi on Bitcoin requires maintaining compatibility with Bitcoin's consensus model and transaction structure. 12 | 4. **Execution Environment**: Smart contracts need a secure, metered execution environment to prevent DoS attacks and ensure reliable operation. 13 | 14 | ## Solution Approach 15 | 16 | ALKANES-RS provides a metaprotocol layer that enables DeFi functionality on Bitcoin without requiring changes to the Bitcoin protocol itself. It achieves this by: 17 | 18 | 1. **Building on Protorunes**: ALKANES is implemented as a subprotocol of runes that is compatible with protorunes, leveraging existing token standards. 19 | 2. **WebAssembly Execution**: Smart contracts are compiled to WebAssembly (WASM) for secure, sandboxed execution. 20 | 3. **METASHREW Integration**: The system integrates with the METASHREW indexer stack for efficient blockchain data processing and state management. 21 | 4. **Fuel Metering**: Computation is metered using a fuel system to prevent DoS attacks, similar to gas on other blockchains. 22 | 23 | ## User Experience Goals 24 | 25 | ALKANES-RS aims to provide: 26 | 27 | 1. **Developer-Friendly Environment**: A Rust-based development environment for writing smart contracts with familiar tools and patterns. 28 | 2. **Familiar DeFi Primitives**: Support for common DeFi operations like token creation, transfers, AMM pools, and more. 29 | 3. **Cross-Network Compatibility**: Support for multiple Bitcoin-based networks including mainnet, testnet, regtest, dogecoin, luckycoin, and bellscoin. 30 | 4. **Efficient State Management**: Optimized state handling for smart contract execution and data persistence. 31 | 5. **Security and Reliability**: Protection against common attack vectors through metered execution and proper isolation. 32 | 33 | ## Target Users 34 | 35 | ALKANES-RS targets several user groups: 36 | 37 | 1. **Bitcoin Developers**: Developers looking to build DeFi applications on Bitcoin without moving to other blockchains. 38 | 2. **DeFi Projects**: Projects wanting to expand their offerings to the Bitcoin ecosystem. 39 | 3. **Bitcoin Holders**: Users who want to participate in DeFi activities while keeping their assets on Bitcoin-based chains. 40 | 4. **Cross-Chain Applications**: Applications that want to provide consistent functionality across multiple blockchain ecosystems. 41 | 42 | ## Competitive Landscape 43 | 44 | ALKANES-RS exists in an ecosystem with other Bitcoin extension protocols: 45 | 46 | 1. **Ordinals and Inscriptions**: Provide NFT-like functionality but lack the programmability for complex DeFi. 47 | 2. **RGB Protocol**: Another smart contract system for Bitcoin with different design choices. 48 | 3. **Stacks**: A separate blockchain with Bitcoin anchoring that enables smart contracts. 49 | 4. **Liquid Network**: A Bitcoin sidechain with some additional scripting capabilities. 50 | 51 | ALKANES-RS differentiates itself through its direct integration with Bitcoin's transaction model, WASM-based execution environment, and focus on DeFi primitives. 52 | 53 | ## Success Metrics 54 | 55 | The success of ALKANES-RS can be measured by: 56 | 57 | 1. **Adoption**: Number of projects building on the ALKANES protocol. 58 | 2. **Transaction Volume**: Amount of Bitcoin value flowing through ALKANES contracts. 59 | 3. **Contract Diversity**: Variety of DeFi applications implemented using ALKANES. 60 | 4. **Developer Experience**: Ease of development and deployment for smart contracts. 61 | 5. **Network Support**: Successful operation across multiple Bitcoin-based networks. 62 | 63 | ## Future Vision 64 | 65 | The long-term vision for ALKANES-RS includes: 66 | 67 | 1. **Expanded Standard Library**: Growing the set of standard contracts for common DeFi patterns. 68 | 2. **Improved Tooling**: Enhanced development, testing, and deployment tools. 69 | 3. **Cross-Protocol Interoperability**: Better integration with other Bitcoin layer 2 solutions. 70 | 4. **Performance Optimization**: Continued improvements to execution efficiency and state management. 71 | 5. **Community Governance**: Potential for community-driven protocol evolution. -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.86.0" 3 | targets = ["wasm32-unknown-unknown"] 4 | -------------------------------------------------------------------------------- /src/etl.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use bitcoin; 3 | use bitcoin::consensus::encode::serialize; 4 | use bitcoin::consensus::encode::Decodable; 5 | use metashrew_core::index_pointer::IndexPointer; 6 | use metashrew_support::index_pointer::KeyValuePointer; 7 | use once_cell::sync::Lazy; 8 | use std::io::Cursor; 9 | use std::sync::Arc; 10 | 11 | pub static BLOCKS: Lazy = Lazy::new(|| IndexPointer::from_keyword("/blockdata/")); 12 | 13 | pub fn index_extensions(height: u32, v: &bitcoin::Block) { 14 | BLOCKS.select_value(height).set(Arc::new(serialize(v))) 15 | } 16 | 17 | pub fn get_block(height: u32) -> Result { 18 | let block_data = BLOCKS.select_value(height).get(); 19 | if block_data.len() == 0 { 20 | return Err(anyhow!("Block not found for height: {}", height)); 21 | } 22 | 23 | let mut cursor = Cursor::new(block_data.as_ref().to_vec()); 24 | bitcoin::Block::consensus_decode(&mut cursor) 25 | .map_err(|e| anyhow!("Failed to decode block: {}", e)) 26 | } 27 | -------------------------------------------------------------------------------- /src/precompiled/.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kungfuflex/alkanes-rs/88d2af6c40e8c6e1551f4b693f722bd31693dac7/src/precompiled/.test -------------------------------------------------------------------------------- /src/precompiled/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alkanes_std_auth_token_build; 2 | pub mod alkanes_std_genesis_alkane_bellscoin_build; 3 | pub mod alkanes_std_genesis_alkane_dogecoin_build; 4 | pub mod alkanes_std_genesis_alkane_fractal_build; 5 | pub mod alkanes_std_genesis_alkane_luckycoin_build; 6 | pub mod alkanes_std_genesis_alkane_mainnet_build; 7 | pub mod alkanes_std_genesis_alkane_regtest_build; 8 | pub mod alkanes_std_owned_token_build; 9 | pub mod alkanes_std_proxy_build; 10 | -------------------------------------------------------------------------------- /src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | 3 | pub mod alkanes; 4 | -------------------------------------------------------------------------------- /src/tables.rs: -------------------------------------------------------------------------------- 1 | use metashrew_core::index_pointer::IndexPointer; 2 | use metashrew_support::index_pointer::KeyValuePointer; 3 | use once_cell::sync::Lazy; 4 | 5 | pub static TRACES: Lazy = Lazy::new(|| IndexPointer::from_keyword("/trace/")); 6 | 7 | pub static TRACES_BY_HEIGHT: Lazy = 8 | Lazy::new(|| IndexPointer::from_keyword("/trace/")); 9 | -------------------------------------------------------------------------------- /src/tests/address.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::Script; 2 | use hex_lit::hex; 3 | use metashrew_core::{println, stdio::stdout}; 4 | use metashrew_support::address::Payload; 5 | use protorune_support::network::{get_network_option, set_network, to_address_str, NetworkParams}; 6 | use std::fmt::Write; 7 | use wasm_bindgen_test::*; 8 | 9 | #[wasm_bindgen_test] 10 | pub fn test_address_generation() { 11 | let saved = get_network_option(); 12 | set_network(NetworkParams { 13 | bech32_prefix: String::from("bcrt"), 14 | p2pkh_prefix: 0x64, 15 | p2sh_prefix: 0xc4, 16 | }); 17 | assert_eq!( 18 | "bcrt1qzr9vhs60g6qlmk7x3dd7g3ja30wyts48sxuemv", 19 | to_address_str(&Script::from_bytes(&hex!( 20 | "001410cacbc34f4681fddbc68b5be4465d8bdc45c2a7" 21 | ))) 22 | .unwrap() 23 | ); 24 | if saved.is_some() { 25 | set_network(saved.unwrap().clone()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/alkane.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::tests::std::alkanes_std_test_build; 4 | use alkanes_support::cellpack::Cellpack; 5 | use alkanes_support::id::AlkaneId; 6 | use anyhow::Result; 7 | use hex; 8 | use metashrew_support::index_pointer::KeyValuePointer; 9 | 10 | use crate::index_block; 11 | use crate::tests::helpers as alkane_helpers; 12 | use alkane_helpers::clear; 13 | use alkanes_support::gz::{compress, decompress}; 14 | #[allow(unused_imports)] 15 | use metashrew_core::{ 16 | index_pointer::IndexPointer, 17 | println, 18 | stdio::{stdout, Write}, 19 | }; 20 | use wasm_bindgen_test::wasm_bindgen_test; 21 | 22 | #[wasm_bindgen_test] 23 | pub fn test_compression() -> Result<()> { 24 | let buffer = alkanes_std_test_build::get_bytes(); 25 | let compressed = compress(buffer.clone())?; 26 | assert_eq!(decompress(compressed)?, buffer.clone()); 27 | Ok(()) 28 | } 29 | #[wasm_bindgen_test] 30 | fn test_extcall() -> Result<()> { 31 | clear(); 32 | let block_height = 840_000; 33 | 34 | let test_cellpacks = [ 35 | //create alkane 36 | Cellpack { 37 | target: AlkaneId { block: 1, tx: 0 }, 38 | inputs: vec![1], 39 | }, 40 | Cellpack { 41 | target: AlkaneId { block: 1, tx: 0 }, 42 | inputs: vec![0], 43 | }, 44 | Cellpack { 45 | target: AlkaneId { block: 2, tx: 0 }, 46 | inputs: vec![50, 1], 47 | }, 48 | ]; 49 | 50 | let test_block = alkane_helpers::init_with_multiple_cellpacks( 51 | alkanes_std_test_build::get_bytes(), 52 | test_cellpacks.to_vec(), 53 | ); 54 | 55 | index_block(&test_block, block_height as u32)?; 56 | Ok(()) 57 | } 58 | #[wasm_bindgen_test] 59 | fn test_transaction() -> Result<()> { 60 | clear(); 61 | let block_height = 840_000; 62 | 63 | let test_cellpacks = [ 64 | //create alkane 65 | Cellpack { 66 | target: AlkaneId { 67 | block: 3, 68 | tx: 10001, 69 | }, 70 | inputs: vec![0, 0], 71 | }, 72 | Cellpack { 73 | target: AlkaneId { 74 | block: 4, 75 | tx: 10001, 76 | }, 77 | inputs: vec![50], 78 | }, 79 | ]; 80 | 81 | let mut test_block = alkane_helpers::init_with_multiple_cellpacks_with_tx( 82 | [alkanes_std_test_build::get_bytes(), vec![]].into(), 83 | test_cellpacks.to_vec(), 84 | ); 85 | index_block(&test_block, block_height as u32)?; 86 | Ok(()) 87 | } 88 | #[wasm_bindgen_test] 89 | fn test_benchmark() -> Result<()> { 90 | clear(); 91 | let block_height = 840_000; 92 | 93 | let test_cellpacks = [ 94 | //create alkane 95 | Cellpack { 96 | target: AlkaneId { block: 1, tx: 0 }, 97 | inputs: vec![78], 98 | }, 99 | /* 100 | //create second alkane 101 | Cellpack { 102 | target: AlkaneId { block: 1, tx: 0 }, 103 | inputs: vec![0], 104 | }, 105 | //target second alkane to be called with custom opcode 106 | Cellpack { 107 | target: AlkaneId { block: 2, tx: 0 }, 108 | inputs: vec![1, 1], 109 | }, 110 | */ 111 | ]; 112 | 113 | let start = metashrew_core::imports::__now(); 114 | let test_block = alkane_helpers::init_with_multiple_cellpacks( 115 | alkanes_std_test_build::get_bytes(), 116 | test_cellpacks.to_vec(), 117 | ); 118 | 119 | index_block(&test_block, block_height as u32)?; 120 | println!("time: {}ms", metashrew_core::imports::__now() - start); 121 | Ok(()) 122 | } 123 | 124 | // #[wasm_bindgen_test] 125 | // async fn test_base_std_functionality() -> Result<()> { 126 | // clear(); 127 | // let test_target = AlkaneId { block: 3, tx: 15 }; 128 | // let test_stored_target = AlkaneId { block: 4, tx: 15 }; 129 | // let input_cellpack = Cellpack { 130 | // target: test_target, 131 | // inputs: vec![0u128], 132 | // }; 133 | 134 | // let test_block = alkane_helpers::init_test_with_cellpack(input_cellpack); 135 | 136 | // index_block(&test_block, 840000 as u32)?; 137 | // /* 138 | // println!("{}", hex::encode(IndexPointer::from_keyword("/alkanes/") 139 | // .select(&test_stored_target.into()) 140 | // .get() 141 | // .as_ref())); 142 | // */ 143 | // assert_eq!( 144 | // IndexPointer::from_keyword("/alkanes/") 145 | // .select(&test_stored_target.into()) 146 | // .get() 147 | // .as_ref() 148 | // .clone(), 149 | // compress(alkanes_std_test_build::get_bytes())? 150 | // ); 151 | 152 | // Ok(()) 153 | // } 154 | } 155 | -------------------------------------------------------------------------------- /src/tests/crash.rs: -------------------------------------------------------------------------------- 1 | use crate::{message::AlkaneMessageContext, tests::std::alkanes_std_auth_token_build}; 2 | use alkanes_support::id::AlkaneId; 3 | use alkanes_support::{cellpack::Cellpack, constants::AUTH_TOKEN_FACTORY_ID}; 4 | use anyhow::{anyhow, Result}; 5 | use bitcoin::OutPoint; 6 | use metashrew_support::{index_pointer::KeyValuePointer, utils::consensus_encode}; 7 | use protorune::{balance_sheet::load_sheet, message::MessageContext, tables::RuneTable}; 8 | use protorune_support::balance_sheet::BalanceSheetOperations; 9 | 10 | use crate::index_block; 11 | use crate::tests::helpers::{self as alkane_helpers, assert_binary_deployed_to_id}; 12 | use crate::tests::std::alkanes_std_owned_token_build; 13 | use alkane_helpers::clear; 14 | #[allow(unused_imports)] 15 | use metashrew_core::{ 16 | println, 17 | stdio::{stdout, Write}, 18 | }; 19 | use wasm_bindgen_test::wasm_bindgen_test; 20 | 21 | #[wasm_bindgen_test] 22 | fn test_owned_token_mint_crash() -> Result<()> { 23 | clear(); 24 | let block_height = 840_000; 25 | 26 | // First deploy auth token factory 27 | let auth_factory_cellpack = Cellpack { 28 | target: AlkaneId { 29 | block: 3, 30 | tx: AUTH_TOKEN_FACTORY_ID, 31 | }, 32 | inputs: vec![100], 33 | }; 34 | 35 | // Deploy and initialize owned token 36 | let owned_token_cellpack = Cellpack { 37 | target: AlkaneId { block: 1, tx: 0 }, 38 | inputs: vec![ 39 | 0, // opcode (initialize) 40 | 1, // auth_token units 41 | 1000, // initial token supply 42 | ], 43 | }; 44 | 45 | // Create mint operation cellpack that causes crash 46 | let mint_cellpack = Cellpack { 47 | target: AlkaneId { block: 2, tx: 1 }, // Points to the owned token 48 | inputs: vec![ 49 | 77, // mint opcode 50 | 500, // amount to mint 51 | ], 52 | }; 53 | 54 | let test_block = alkane_helpers::init_with_multiple_cellpacks_with_tx( 55 | [ 56 | alkanes_std_auth_token_build::get_bytes(), 57 | alkanes_std_owned_token_build::get_bytes(), 58 | ] 59 | .into(), 60 | [auth_factory_cellpack, owned_token_cellpack].into(), 61 | ); 62 | 63 | println!("STEP 1: Indexing initial deployment block..."); 64 | index_block(&test_block, block_height)?; 65 | println!("STEP 1: Initial deployment block indexed successfully"); 66 | 67 | let owned_token_id = AlkaneId { block: 2, tx: 1 }; 68 | let auth_token_id = AlkaneId { block: 2, tx: 2 }; 69 | println!( 70 | "STEP 2: Created token IDs: owned={:?}, auth={:?}", 71 | owned_token_id, auth_token_id 72 | ); 73 | 74 | // Verify initial state 75 | let tx = test_block.txdata.last().ok_or(anyhow!("no last el"))?; 76 | let outpoint = OutPoint { 77 | txid: tx.compute_txid(), 78 | vout: 0, 79 | }; 80 | println!("STEP 3: Got outpoint: {:?}", outpoint); 81 | 82 | println!("STEP 4: Loading initial balance sheet..."); 83 | let sheet = load_sheet( 84 | &RuneTable::for_protocol(AlkaneMessageContext::protocol_tag()) 85 | .OUTPOINT_TO_RUNES 86 | .select(&consensus_encode(&outpoint)?), 87 | ); 88 | println!("STEP 4: Balance sheet loaded successfully"); 89 | 90 | // Verify initial balances 91 | let owned_balance = sheet.get_cached(&owned_token_id.into()); 92 | let auth_balance = sheet.get_cached(&auth_token_id.into()); 93 | println!( 94 | "STEP 5: Initial balances - owned: {}, auth: {}", 95 | owned_balance, auth_balance 96 | ); 97 | assert_eq!(owned_balance, 1000, "Initial token balance incorrect"); 98 | assert_eq!(auth_balance, 1, "Auth token balance incorrect"); 99 | 100 | println!("STEP 6: Creating mint block..."); 101 | let mint_block = alkane_helpers::init_with_multiple_cellpacks( 102 | alkanes_std_owned_token_build::get_bytes(), 103 | vec![mint_cellpack.clone()], 104 | ); 105 | println!("STEP 6: Mint block created successfully"); 106 | 107 | println!("STEP 7: About to index mint block..."); 108 | 109 | index_block(&mint_block, block_height + 1)?; 110 | println!("STEP 8: Mint block indexed successfully"); 111 | 112 | // Get the mint transaction info 113 | println!("STEP 9: Checking mint transaction state..."); 114 | let mint_tx = mint_block.txdata.last().ok_or(anyhow!("no mint tx"))?; 115 | let mint_outpoint = OutPoint { 116 | txid: mint_tx.compute_txid(), 117 | vout: 0, 118 | }; 119 | let mint_sheet = load_sheet( 120 | &RuneTable::for_protocol(AlkaneMessageContext::protocol_tag()) 121 | .OUTPOINT_TO_RUNES 122 | .select(&consensus_encode(&mint_outpoint)?), 123 | ); 124 | println!( 125 | "STEP 10: Mint state - txid: {}, balances: {:?}", 126 | mint_tx.compute_txid(), 127 | mint_sheet.balances() 128 | ); 129 | 130 | println!("Test completed successfully - no crash occurred"); 131 | 132 | Ok(()) 133 | } 134 | -------------------------------------------------------------------------------- /src/tests/determinism.rs: -------------------------------------------------------------------------------- 1 | use crate::tests::std::alkanes_std_test_build; 2 | use alkanes_support::cellpack::Cellpack; 3 | use alkanes_support::id::AlkaneId; 4 | use alkanes_support::trace::{Trace, TraceEvent}; 5 | use anyhow::Result; 6 | use bitcoin::{OutPoint, ScriptBuf, Sequence, TxIn, Witness}; 7 | use protorune_support::protostone::ProtostoneEdict; 8 | 9 | use crate::index_block; 10 | use crate::tests::helpers::{self as alkane_helpers, get_sheet_for_runtime}; 11 | use alkane_helpers::clear; 12 | use alkanes::view; 13 | #[allow(unused_imports)] 14 | use metashrew_core::{ 15 | println, 16 | stdio::{stdout, Write}, 17 | }; 18 | use protorune_support::balance_sheet::ProtoruneRuneId; 19 | use wasm_bindgen_test::wasm_bindgen_test; 20 | 21 | #[wasm_bindgen_test] 22 | fn test_incoming_alkanes_ordered() -> Result<()> { 23 | clear(); 24 | let block_height = 840_000; 25 | 26 | // Create a cellpack to call the process_numbers method (opcode 11) 27 | let self_mint_cellpack = Cellpack { 28 | target: AlkaneId { block: 1, tx: 0 }, 29 | inputs: vec![22, 1000], 30 | }; 31 | let copy_mint_cellpack = Cellpack { 32 | target: AlkaneId { block: 5, tx: 1 }, 33 | inputs: vec![22, 1000], 34 | }; 35 | 36 | let test_order_cellpack = Cellpack { 37 | target: AlkaneId { block: 2, tx: 1 }, 38 | inputs: vec![6], 39 | }; 40 | // Initialize the contract and execute the cellpacks 41 | let mut test_block = alkane_helpers::init_with_multiple_cellpacks_with_tx( 42 | [alkanes_std_test_build::get_bytes()].into(), 43 | [self_mint_cellpack].into(), 44 | ); 45 | 46 | for i in 1..10 { 47 | test_block.txdata.push( 48 | alkane_helpers::create_multiple_cellpack_with_witness_and_in( 49 | Witness::new(), 50 | vec![copy_mint_cellpack.clone()], 51 | OutPoint { 52 | txid: test_block.txdata[i].compute_txid(), 53 | vout: 0, 54 | }, 55 | false, 56 | ), 57 | ); 58 | } 59 | 60 | test_block.txdata.push( 61 | alkane_helpers::create_multiple_cellpack_with_witness_and_in( 62 | Witness::new(), 63 | vec![test_order_cellpack.clone()], 64 | OutPoint { 65 | txid: test_block.txdata[test_block.txdata.len() - 1].compute_txid(), 66 | vout: 0, 67 | }, 68 | false, 69 | ), 70 | ); 71 | 72 | index_block(&test_block, block_height)?; 73 | 74 | let outpoint = OutPoint { 75 | txid: test_block.txdata[test_block.txdata.len() - 1].compute_txid(), 76 | vout: 3, 77 | }; 78 | 79 | let trace_data: Trace = view::trace(&outpoint)?.try_into()?; 80 | 81 | let last_trace_event = trace_data.0.lock().expect("Mutex poisoned").last().cloned(); 82 | 83 | // Access the data field from the trace response 84 | if let Some(return_context) = last_trace_event { 85 | // Use pattern matching to extract the data field from the TraceEvent enum 86 | match return_context { 87 | TraceEvent::ReturnContext(trace_response) => {} 88 | _ => panic!("Expected ReturnContext variant, but got a different variant"), 89 | } 90 | } else { 91 | panic!("Failed to get last_trace_event from trace data"); 92 | } 93 | 94 | Ok(()) 95 | } 96 | -------------------------------------------------------------------------------- /src/tests/forge.rs: -------------------------------------------------------------------------------- 1 | use crate::{message::AlkaneMessageContext, tests::std::alkanes_std_auth_token_build}; 2 | use alkanes_support::id::AlkaneId; 3 | use alkanes_support::{cellpack::Cellpack, constants::AUTH_TOKEN_FACTORY_ID}; 4 | use anyhow::{anyhow, Result}; 5 | use bitcoin::{Sequence, Witness, Transaction, Address, Amount, Block, TxOut, TxIn, ScriptBuf, OutPoint}; 6 | use ordinals::{Runestone}; 7 | use bitcoin::address::{NetworkChecked}; 8 | use protorune_support::balance_sheet::ProtoruneRuneId; 9 | use metashrew_support::{index_pointer::KeyValuePointer, utils::consensus_encode}; 10 | use protorune::{test_helpers as helpers, balance_sheet::load_sheet, message::MessageContext, tables::RuneTable}; 11 | use bitcoin::transaction::{Version}; 12 | use protorune::test_helpers::{get_address}; 13 | use protorune::protostone::Protostones; 14 | use protorune_support::protostone::ProtostoneEdict; 15 | use ordinals::{Artifact}; 16 | 17 | use crate::index_block; 18 | use protorune_support::protostone::Protostone; 19 | use crate::tests::helpers::{self as alkane_helpers, assert_binary_deployed_to_id}; 20 | use crate::tests::std::alkanes_std_owned_token_build; 21 | use alkane_helpers::clear; 22 | #[allow(unused_imports)] 23 | use metashrew_core::{ 24 | println, 25 | stdio::{stdout, Write}, 26 | }; 27 | use wasm_bindgen_test::wasm_bindgen_test; 28 | 29 | pub fn create_protostone_encoded_transaction( 30 | previous_output: OutPoint, 31 | protostones: Vec, 32 | ) -> Transaction { 33 | let input_script = ScriptBuf::new(); 34 | 35 | // Create a transaction input 36 | let txin = TxIn { 37 | previous_output, 38 | script_sig: input_script, 39 | sequence: Sequence::MAX, 40 | witness: Witness::new(), 41 | }; 42 | 43 | let address: Address = get_address(&helpers::ADDRESS1().as_str()); 44 | 45 | let script_pubkey = address.script_pubkey(); 46 | 47 | let txout = TxOut { 48 | value: Amount::from_sat(100_000_000), 49 | script_pubkey, 50 | }; 51 | 52 | let runestone: ScriptBuf = (Runestone { 53 | etching: None, 54 | pointer: None, // points to the OP_RETURN, so therefore targets the protoburn 55 | edicts: vec![], 56 | mint: None, 57 | protocol: match protostones.encipher() { 58 | Ok(v) => Some(v), 59 | Err(_) => None, 60 | }, 61 | }) 62 | .encipher(); 63 | 64 | // op return is at output 1 65 | let op_return = TxOut { 66 | value: Amount::from_sat(0), 67 | script_pubkey: runestone, 68 | }; 69 | 70 | Transaction { 71 | version: Version::ONE, 72 | lock_time: bitcoin::absolute::LockTime::ZERO, 73 | input: vec![txin], 74 | output: vec![txout, op_return], 75 | } 76 | } 77 | 78 | #[wasm_bindgen_test] 79 | fn test_cant_forge_edicts() -> Result<()> { 80 | clear(); 81 | let block_height = 840_000; 82 | let mut test_block: Block = helpers::create_block_with_coinbase_tx(block_height); 83 | let outpoint = OutPoint { 84 | txid: test_block.txdata[0].compute_txid(), 85 | vout: 0 86 | }; 87 | test_block.txdata.push(create_protostone_encoded_transaction(outpoint, vec![Protostone { 88 | protocol_tag: 1, 89 | from: None, 90 | edicts: vec![ProtostoneEdict { 91 | id: ProtoruneRuneId { 92 | block: 2, 93 | tx: 100 94 | }, 95 | amount: 100000, 96 | output: 0 97 | }], 98 | pointer: Some(0), 99 | refund: Some(0), 100 | message: vec![], 101 | burn: None 102 | }])); 103 | index_block(&test_block, block_height)?; 104 | let edict_outpoint = OutPoint { 105 | txid: test_block.txdata[test_block.txdata.len() - 1].compute_txid(), 106 | vout: 0 107 | }; 108 | let sheet = load_sheet( 109 | &RuneTable::for_protocol(AlkaneMessageContext::protocol_tag()) 110 | .OUTPOINT_TO_RUNES 111 | .select(&consensus_encode(&edict_outpoint)?), 112 | ); 113 | println!("{:?}", sheet); 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /src/tests/fuel.rs: -------------------------------------------------------------------------------- 1 | use crate::tests::std::alkanes_std_test_build; 2 | use alkanes_support::cellpack::Cellpack; 3 | use alkanes_support::id::AlkaneId; 4 | use alkanes_support::trace::{Trace, TraceEvent}; 5 | use anyhow::Result; 6 | use bitcoin::OutPoint; 7 | 8 | use crate::index_block; 9 | use crate::tests::helpers::{self as alkane_helpers}; 10 | use alkane_helpers::clear; 11 | use alkanes::view; 12 | #[allow(unused_imports)] 13 | use metashrew_core::{ 14 | println, 15 | stdio::{stdout, Write}, 16 | }; 17 | use wasm_bindgen_test::wasm_bindgen_test; 18 | 19 | #[wasm_bindgen_test] 20 | fn test_infinite_loop() -> Result<()> { 21 | clear(); 22 | let block_height = 840_000; 23 | 24 | // Create a cellpack to call the process_numbers method (opcode 11) 25 | let infinite_exec_cellpack = Cellpack { 26 | target: AlkaneId { block: 1, tx: 0 }, 27 | inputs: vec![20], 28 | }; 29 | 30 | // Initialize the contract and execute the cellpacks 31 | let mut test_block = alkane_helpers::init_with_multiple_cellpacks_with_tx( 32 | [alkanes_std_test_build::get_bytes()].into(), 33 | [infinite_exec_cellpack].into(), 34 | ); 35 | 36 | index_block(&test_block, block_height)?; 37 | 38 | let outpoint = OutPoint { 39 | txid: test_block.txdata.last().unwrap().compute_txid(), 40 | vout: 3, 41 | }; 42 | 43 | let trace_data: Trace = view::trace(&outpoint)?.try_into()?; 44 | let trace_events = trace_data.0.lock().expect("Mutex poisoned"); 45 | let last_trace_event = trace_events[trace_events.len() - 1].clone(); 46 | match last_trace_event { 47 | TraceEvent::RevertContext(trace_response) => { 48 | // Now we have the TraceResponse, access the data field 49 | let data = String::from_utf8_lossy(&trace_response.inner.data); 50 | assert!(data.contains("ALKANES: revert: all fuel consumed by WebAssembly")); 51 | } 52 | _ => panic!("Expected RevertContext variant, but got a different variant"), 53 | } 54 | 55 | Ok(()) 56 | } 57 | 58 | #[wasm_bindgen_test] 59 | fn test_infinite_extcall_loop() -> Result<()> { 60 | clear(); 61 | let block_height = 840_000; 62 | 63 | // Create a cellpack to call the process_numbers method (opcode 11) 64 | let infinite_exec_cellpack = Cellpack { 65 | target: AlkaneId { block: 1, tx: 0 }, 66 | inputs: vec![21], 67 | }; 68 | 69 | // Initialize the contract and execute the cellpacks 70 | let mut test_block = alkane_helpers::init_with_multiple_cellpacks_with_tx( 71 | [alkanes_std_test_build::get_bytes()].into(), 72 | [infinite_exec_cellpack].into(), 73 | ); 74 | 75 | index_block(&test_block, block_height)?; 76 | 77 | let outpoint = OutPoint { 78 | txid: test_block.txdata.last().unwrap().compute_txid(), 79 | vout: 3, 80 | }; 81 | 82 | let trace_data: Trace = view::trace(&outpoint)?.try_into()?; 83 | let trace_events = trace_data.0.lock().expect("Mutex poisoned"); 84 | let last_trace_event = trace_events[trace_events.len() - 1].clone(); 85 | match last_trace_event { 86 | TraceEvent::RevertContext(trace_response) => { 87 | // Now we have the TraceResponse, access the data field 88 | let data = String::from_utf8_lossy(&trace_response.inner.data); 89 | assert!(data 90 | .contains("Possible infinite recursion encountered: checkpoint depth too large")); 91 | } 92 | _ => panic!("Expected RevertContext variant, but got a different variant"), 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "test-utils", test))] 2 | pub mod helpers; 3 | #[cfg(test)] 4 | pub mod std; 5 | #[cfg(test)] 6 | pub mod utils; 7 | //pub mod index_alkanes; 8 | #[cfg(test)] 9 | pub mod abi_test; 10 | #[cfg(test)] 11 | //pub mod address; 12 | #[cfg(test)] 13 | pub mod alkane; 14 | #[cfg(test)] 15 | pub mod arbitrary_alkane_mint; 16 | #[cfg(test)] 17 | pub mod auth_token; 18 | #[cfg(test)] 19 | pub mod crash; 20 | #[cfg(test)] 21 | pub mod determinism; 22 | #[cfg(test)] 23 | pub mod edict_then_message; 24 | #[cfg(test)] 25 | pub mod forge; 26 | #[cfg(test)] 27 | pub mod fuel; 28 | #[cfg(test)] 29 | pub mod genesis; 30 | #[cfg(test)] 31 | pub mod memory_security_tests; 32 | #[cfg(test)] 33 | pub mod networks; 34 | #[cfg(test)] 35 | pub mod serialization; 36 | #[cfg(test)] 37 | pub mod special_extcall; 38 | #[cfg(test)] 39 | pub mod upgradeable; 40 | #[cfg(test)] 41 | pub mod vec_input_test; 42 | #[cfg(test)] 43 | pub mod view; 44 | -------------------------------------------------------------------------------- /src/tests/networks.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "regtest")] 2 | mod networks { 3 | use wasm_bindgen_test::*; 4 | 5 | #[wasm_bindgen_test] 6 | fn test_protorunes_view_by_address() {} 7 | } 8 | -------------------------------------------------------------------------------- /src/tests/serialization.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use hex_lit::hex; 3 | use metashrew_core::{println, stdio::stdout}; 4 | use protobuf::{Message, MessageField}; 5 | use protorune_support::proto::protorune::ProtorunesWalletRequest; 6 | use std::fmt::Write; 7 | use wasm_bindgen_test::*; 8 | 9 | #[wasm_bindgen_test] 10 | pub fn test_decode() -> Result<()> { 11 | println!("{:?}", ProtorunesWalletRequest::parse_from_bytes(&(&hex!("0a406263727431703335687775396a306132377a637a6c6468337a36686e796b637972386a3577766837307a706c796a68616e377a647036763577736a6a75716430")).to_vec()).unwrap()); 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /src/tests/special_extcall.rs: -------------------------------------------------------------------------------- 1 | use crate::index_block; 2 | use crate::tests::helpers::{self as alkane_helpers}; 3 | use crate::tests::std::alkanes_std_test_build; 4 | use alkane_helpers::clear; 5 | use alkanes::view; 6 | use alkanes_support::cellpack::Cellpack; 7 | use alkanes_support::id::AlkaneId; 8 | use alkanes_support::trace::{Trace, TraceEvent}; 9 | use anyhow::Result; 10 | use bitcoin::block::Header; 11 | use bitcoin::OutPoint; 12 | use bitcoin::Transaction; 13 | #[allow(unused_imports)] 14 | use metashrew_core::{ 15 | println, 16 | stdio::{stdout, Write}, 17 | }; 18 | use protorune::test_helpers::create_coinbase_transaction; 19 | use protorune_support::utils::consensus_decode; 20 | use wasm_bindgen_test::wasm_bindgen_test; 21 | 22 | #[wasm_bindgen_test] 23 | fn test_special_extcall() -> Result<()> { 24 | clear(); 25 | let block_height = 840_000; 26 | 27 | let get_header = Cellpack { 28 | target: AlkaneId { block: 1, tx: 0 }, 29 | inputs: vec![101], 30 | }; 31 | let coinbase_tx = Cellpack { 32 | target: AlkaneId { block: 2, tx: 1 }, 33 | inputs: vec![102], 34 | }; 35 | 36 | // Initialize the contract and execute the cellpacks 37 | let mut test_block = alkane_helpers::init_with_multiple_cellpacks_with_tx( 38 | [alkanes_std_test_build::get_bytes(), [].into()].into(), 39 | [get_header, coinbase_tx].into(), 40 | ); 41 | 42 | for i in 0..5000 { 43 | test_block 44 | .txdata 45 | .push(create_coinbase_transaction(block_height)); 46 | } 47 | 48 | index_block(&test_block, block_height)?; 49 | 50 | let outpoint_1 = OutPoint { 51 | txid: test_block.txdata[1].compute_txid(), 52 | vout: 3, 53 | }; 54 | 55 | let raw_trace_data = view::trace(&outpoint_1)?; 56 | let trace_data: Trace = raw_trace_data.clone().try_into()?; 57 | 58 | let trace_event_1 = trace_data.0.lock().expect("Mutex poisoned").last().cloned(); 59 | 60 | // Access the data field from the trace response 61 | if let Some(return_context) = trace_event_1 { 62 | // Use pattern matching to extract the data field from the TraceEvent enum 63 | match return_context { 64 | TraceEvent::ReturnContext(trace_response) => { 65 | // Now we have the TraceResponse, access the data field 66 | let data = consensus_decode::

(&mut std::io::Cursor::new( 67 | trace_response.inner.data, 68 | ))?; 69 | 70 | println!("{:?}", data); 71 | assert_eq!(data.time, 1231006505); 72 | } 73 | _ => panic!("Expected ReturnContext variant, but got a different variant"), 74 | } 75 | } else { 76 | panic!("Failed to get trace_event_1 from trace data"); 77 | } 78 | 79 | let outpoint_2 = OutPoint { 80 | txid: test_block.txdata[2].compute_txid(), 81 | vout: 3, 82 | }; 83 | 84 | let raw_trace_data = view::trace(&outpoint_2)?; 85 | let trace_data: Trace = raw_trace_data.clone().try_into()?; 86 | 87 | let trace_event_1 = trace_data.0.lock().expect("Mutex poisoned").last().cloned(); 88 | 89 | // Access the data field from the trace response 90 | if let Some(return_context) = trace_event_1 { 91 | // Use pattern matching to extract the data field from the TraceEvent enum 92 | match return_context { 93 | TraceEvent::ReturnContext(trace_response) => { 94 | // Now we have the TraceResponse, access the data field 95 | let data = consensus_decode::(&mut std::io::Cursor::new( 96 | trace_response.inner.data, 97 | ))?; 98 | 99 | println!("{:?}", data); 100 | assert_eq!(data.version, bitcoin::transaction::Version(2)); 101 | } 102 | _ => panic!("Expected ReturnContext variant, but got a different variant"), 103 | } 104 | } else { 105 | panic!("Failed to get trace_event_1 from trace data"); 106 | } 107 | 108 | Ok(()) 109 | } 110 | -------------------------------------------------------------------------------- /src/tests/static/849236.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kungfuflex/alkanes-rs/88d2af6c40e8c6e1551f4b693f722bd31693dac7/src/tests/static/849236.txt -------------------------------------------------------------------------------- /src/tests/static/849250.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kungfuflex/alkanes-rs/88d2af6c40e8c6e1551f4b693f722bd31693dac7/src/tests/static/849250.txt -------------------------------------------------------------------------------- /src/tests/trace.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::{Transaction, Block}; 2 | use ordinals::{Runestone}; 3 | use wasm_bindgen_test::prelude::*; 4 | use protorune::test_helpers::{create_block_with_coinbase_tx, get_address, ADDRESS1}; 5 | use anyhow::{Result}; 6 | 7 | #[wasm_bindgen_test] 8 | pub fn test_trace() -> Result<()> { 9 | let height = 840_000; 10 | let block: Block = create_block_with_coinbase_tx(height); 11 | } 12 | -------------------------------------------------------------------------------- /src/tests/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::tests::helpers::clear; 4 | use anyhow::Result; 5 | #[allow(unused_imports)] 6 | use metashrew_core::{ 7 | index_pointer::IndexPointer, 8 | println, 9 | stdio::{stdout, Write}, 10 | }; 11 | use wasm_bindgen_test::wasm_bindgen_test; 12 | 13 | /* 14 | #[wasm_bindgen_test] 15 | fn test_response_serialization() -> Result<()> { 16 | clear(); 17 | Ok(()) 18 | } 19 | */ 20 | } 21 | -------------------------------------------------------------------------------- /src/tests/view.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::tests::helpers::clear; 4 | use crate::view::{call_view, get_statics, NAME_OPCODE, STATIC_FUEL, SYMBOL_OPCODE}; 5 | use alkanes_support::id::AlkaneId; 6 | use anyhow::Result; 7 | use std::sync::atomic::{AtomicUsize, Ordering}; 8 | use std::sync::Arc; 9 | use wasm_bindgen_test::wasm_bindgen_test; 10 | 11 | // Create a static counter to track the number of calls to call_view 12 | static CALL_VIEW_COUNTER: AtomicUsize = AtomicUsize::new(0); 13 | 14 | // Mock the call_view function to track calls 15 | fn setup_test_environment() { 16 | // Reset the counter before each test 17 | CALL_VIEW_COUNTER.store(0, Ordering::SeqCst); 18 | } 19 | 20 | #[wasm_bindgen_test] 21 | fn test_get_statics_caching() -> Result<()> { 22 | clear(); 23 | setup_test_environment(); 24 | 25 | // Create a test AlkaneId 26 | let test_id = AlkaneId { 27 | block: 123, 28 | tx: 456, 29 | }; 30 | 31 | // First call to get_statics should call call_view twice (once for name, once for symbol) 32 | let (name1, symbol1) = get_statics(&test_id); 33 | 34 | // Make sure we got valid results 35 | assert!(!name1.is_empty()); 36 | assert!(!symbol1.is_empty()); 37 | 38 | // Second call to get_statics with the same ID should use the cache 39 | let (name2, symbol2) = get_statics(&test_id); 40 | 41 | // Verify the results are the same 42 | assert_eq!(name1, name2); 43 | assert_eq!(symbol1, symbol2); 44 | 45 | // Create a different AlkaneId 46 | let different_id = AlkaneId { 47 | block: 789, 48 | tx: 101, 49 | }; 50 | 51 | // Call with a different ID should not use the cache 52 | let (different_name, different_symbol) = get_statics(&different_id); 53 | 54 | // Make sure we got valid results for the different ID 55 | assert!(!different_name.is_empty()); 56 | assert!(!different_symbol.is_empty()); 57 | 58 | // Call again with the original ID should still use the cache 59 | let (name3, symbol3) = get_statics(&test_id); 60 | 61 | // Verify the results are still the same 62 | assert_eq!(name1, name3); 63 | assert_eq!(symbol1, symbol3); 64 | 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/trace.rs: -------------------------------------------------------------------------------- 1 | use crate::tables::{TRACES, TRACES_BY_HEIGHT}; 2 | use alkanes_support::proto; 3 | use alkanes_support::trace::Trace; 4 | use anyhow::Result; 5 | use bitcoin::OutPoint; 6 | use metashrew_support::index_pointer::KeyValuePointer; 7 | use metashrew_support::utils::consensus_encode; 8 | use protobuf::Message; 9 | use std::sync::Arc; 10 | #[allow(unused_imports)] 11 | use { 12 | metashrew_core::{println, stdio::stdout}, 13 | std::fmt::Write, 14 | }; 15 | 16 | pub fn save_trace(outpoint: &OutPoint, height: u64, trace: Trace) -> Result<()> { 17 | let buffer: Vec = consensus_encode::(outpoint)?; 18 | TRACES.select(&buffer).set(Arc::>::new( 19 | >::into(trace).write_to_bytes()?, 20 | )); 21 | TRACES_BY_HEIGHT 22 | .select_value(height) 23 | .append(Arc::new(buffer)); 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /src/vm/constants.rs: -------------------------------------------------------------------------------- 1 | pub(super) const MEMORY_LIMIT: usize = 43554432; 2 | -------------------------------------------------------------------------------- /src/vm/exports.rs: -------------------------------------------------------------------------------- 1 | use super::{AlkanesInstance, AlkanesState}; 2 | use alkanes_support::{ 3 | parcel::AlkaneTransferParcel, response::ExtendedCallResponse, storage::StorageMap, 4 | }; 5 | use anyhow::{anyhow, Result}; 6 | use metashrew_support::utils::{consume_exact, consume_sized_int, consume_to_end}; 7 | use wasmi::*; 8 | 9 | pub struct AlkanesExportsImpl(()); 10 | impl AlkanesExportsImpl { 11 | pub fn _get_export(vm: &mut AlkanesInstance, name: &str) -> Result { 12 | let instance: &mut Instance = &mut vm.instance; 13 | let store: &mut Store = &mut vm.store; 14 | Ok(instance.get_func(store, name).ok_or("").map_err(|_| { 15 | anyhow!(format!( 16 | "{} not found -- is this WASM built with the ALKANES SDK?", 17 | name 18 | )) 19 | })?) 20 | } 21 | pub fn _get_result(vm: &mut AlkanesInstance, result: &[Val; 1]) -> Result> { 22 | vm.read_arraybuffer( 23 | result[0] 24 | .i32() 25 | .ok_or("") 26 | .map_err(|_| anyhow!("result is not an i32"))?, 27 | ) 28 | } 29 | 30 | pub fn parse(cursor: &mut std::io::Cursor>) -> Result { 31 | let alkanes = AlkaneTransferParcel::parse(cursor)?; 32 | let storage = { 33 | let mut pairs = Vec::<(Vec, Vec)>::new(); 34 | let len = consume_sized_int::(cursor)? as u64; 35 | if len > 0 { 36 | for _i in 0..len { 37 | let key_length: usize = consume_sized_int::(cursor)?.try_into()?; 38 | let key: Vec = consume_exact(cursor, key_length)?; 39 | let value_length: usize = consume_sized_int::(cursor)?.try_into()?; 40 | let value: Vec = consume_exact(cursor, value_length)?; 41 | pairs.push((key, value)); 42 | } 43 | } 44 | StorageMap::from_iter(pairs.into_iter()) 45 | }; 46 | let data = consume_to_end(cursor)?; 47 | Ok(ExtendedCallResponse { 48 | alkanes, 49 | storage, 50 | data, 51 | }) 52 | } 53 | pub fn execute(vm: &mut AlkanesInstance) -> Result { 54 | let mut result = [Val::I32(0)]; 55 | let func = Self::_get_export(vm, "__execute")?; 56 | func.call(&mut vm.store, &[], &mut result)?; 57 | println!("fuel left: {}", vm.store.get_fuel()?); 58 | let response = ExtendedCallResponse::parse(&mut std::io::Cursor::new(Self::_get_result( 59 | vm, &result, 60 | )?))?; 61 | Ok(response) 62 | } 63 | 64 | pub fn call_meta(vm: &mut AlkanesInstance) -> Result> { 65 | let mut result = [Val::I32(0)]; 66 | let func = Self::_get_export(vm, "__meta")?; 67 | func.call(&mut vm.store, &[], &mut result)?; 68 | Self::_get_result(vm, &result) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/vm/extcall.rs: -------------------------------------------------------------------------------- 1 | use alkanes_support::id::AlkaneId; 2 | use alkanes_support::trace::{TraceContext, TraceEvent}; 3 | use metashrew_core::index_pointer::AtomicPointer; 4 | 5 | pub trait Extcall { 6 | fn isdelegate() -> bool; 7 | fn isstatic() -> bool; 8 | fn event(context: TraceContext) -> TraceEvent; 9 | fn handle_atomic(atomic: &mut AtomicPointer) { 10 | if Self::isstatic() { 11 | atomic.rollback(); 12 | } else { 13 | atomic.commit(); 14 | } 15 | } 16 | fn change_context( 17 | target: AlkaneId, 18 | caller: AlkaneId, 19 | myself: AlkaneId, 20 | ) -> (AlkaneId, AlkaneId) { 21 | if Self::isdelegate() { 22 | (caller, myself) 23 | } else { 24 | (myself, target) 25 | } 26 | } 27 | } 28 | 29 | pub struct Call(()); 30 | 31 | impl Extcall for Call { 32 | fn isdelegate() -> bool { 33 | false 34 | } 35 | fn isstatic() -> bool { 36 | false 37 | } 38 | fn event(context: TraceContext) -> TraceEvent { 39 | TraceEvent::EnterCall(context) 40 | } 41 | } 42 | 43 | pub struct Delegatecall(()); 44 | 45 | impl Extcall for Delegatecall { 46 | fn isdelegate() -> bool { 47 | true 48 | } 49 | fn isstatic() -> bool { 50 | false 51 | } 52 | fn event(context: TraceContext) -> TraceEvent { 53 | TraceEvent::EnterDelegatecall(context) 54 | } 55 | } 56 | 57 | pub struct Staticcall(()); 58 | 59 | impl Extcall for Staticcall { 60 | fn isdelegate() -> bool { 61 | false 62 | } 63 | fn isstatic() -> bool { 64 | true 65 | } 66 | fn event(context: TraceContext) -> TraceEvent { 67 | TraceEvent::EnterStaticcall(context) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/vm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod exports; 3 | pub mod extcall; 4 | pub mod fuel; 5 | pub mod host_functions; 6 | pub mod instance; 7 | pub mod runtime; 8 | pub mod state; 9 | pub mod utils; 10 | 11 | use self::constants::*; 12 | use self::exports::*; 13 | use self::extcall::*; 14 | use self::host_functions::*; 15 | use self::instance::*; 16 | use self::runtime::*; 17 | use self::state::*; 18 | use self::utils::*; 19 | -------------------------------------------------------------------------------- /src/vm/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use alkanes_support::{ 4 | cellpack::Cellpack, context::Context, id::AlkaneId, parcel::AlkaneTransferParcel, trace::Trace, 5 | }; 6 | #[allow(unused_imports)] 7 | use { 8 | metashrew_core::{println, stdio::stdout}, 9 | std::fmt::Write, 10 | }; 11 | 12 | use protorune::message::MessageContextParcel; 13 | 14 | #[derive(Default, Clone)] 15 | pub struct AlkanesRuntimeContext { 16 | pub myself: AlkaneId, 17 | pub caller: AlkaneId, 18 | pub incoming_alkanes: AlkaneTransferParcel, 19 | pub returndata: Vec, 20 | pub inputs: Vec, 21 | pub message: Box, 22 | pub trace: Trace, 23 | } 24 | 25 | impl fmt::Debug for AlkanesRuntimeContext { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.debug_struct("AlkanesRuntimeContext") 28 | .field("myself", &self.myself) 29 | .field("caller", &self.caller) 30 | .field("incoming_alkanes", &self.incoming_alkanes) 31 | .field("inputs", &self.inputs) 32 | .finish() 33 | } 34 | } 35 | 36 | impl AlkanesRuntimeContext { 37 | pub fn from_parcel_and_cellpack( 38 | message: &MessageContextParcel, 39 | cellpack: &Cellpack, 40 | ) -> AlkanesRuntimeContext { 41 | let cloned = cellpack.clone(); 42 | let message_copy = message.clone(); 43 | let incoming_alkanes = message_copy.runes.clone().into(); 44 | AlkanesRuntimeContext { 45 | message: Box::new(message_copy), 46 | returndata: vec![], 47 | incoming_alkanes, 48 | myself: AlkaneId::default(), 49 | caller: AlkaneId::default(), 50 | trace: Trace::default(), 51 | inputs: cloned.inputs, 52 | } 53 | } 54 | pub fn flatten(&self) -> Vec { 55 | let mut result = Vec::::new(); 56 | result.push(self.myself.block); 57 | result.push(self.myself.tx); 58 | result.push(self.caller.block); 59 | result.push(self.caller.tx); 60 | result.push(self.message.vout as u128); 61 | result.push(self.incoming_alkanes.0.len() as u128); 62 | for incoming in &self.incoming_alkanes.0 { 63 | result.push(incoming.id.block); 64 | result.push(incoming.id.tx); 65 | result.push(incoming.value); 66 | } 67 | for input in self.inputs.clone() { 68 | result.push(input); 69 | } 70 | result 71 | } 72 | pub fn serialize(&self) -> Vec { 73 | let result = self 74 | .flatten() 75 | .into_iter() 76 | .map(|v| { 77 | let ar = (&v.to_le_bytes()).to_vec(); 78 | ar 79 | }) 80 | .flatten() 81 | .collect::>(); 82 | result 83 | } 84 | pub fn flat(&self) -> Context { 85 | Context { 86 | myself: self.myself.clone(), 87 | caller: self.caller.clone(), 88 | vout: self.message.vout, 89 | incoming_alkanes: self.incoming_alkanes.clone(), 90 | inputs: self.inputs.clone(), 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/vm/state.rs: -------------------------------------------------------------------------------- 1 | use super::AlkanesRuntimeContext; 2 | use std::sync::{Arc, Mutex}; 3 | use wasmi::*; 4 | 5 | pub struct AlkanesState { 6 | pub(super) had_failure: bool, 7 | pub(super) context: Arc>, 8 | pub(super) limiter: StoreLimits, 9 | } 10 | -------------------------------------------------------------------------------- /update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if a version argument was provided 4 | if [ $# -ne 1 ]; then 5 | echo "Usage: $0 " 6 | echo "Example: $0 0.2.0" 7 | exit 1 8 | fi 9 | 10 | NEW_VERSION=$1 11 | 12 | # Validate version format (basic check for semver format) 13 | if ! [[ $NEW_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.]+)?(\+[a-zA-Z0-9\.]+)?$ ]]; then 14 | echo "Error: Version must be in semver format (e.g., 1.2.3, 1.2.3-alpha, 1.2.3+build.1)" 15 | exit 1 16 | fi 17 | 18 | echo "Updating all crates to version $NEW_VERSION" 19 | 20 | # Find all Cargo.toml files in the repository 21 | CARGO_FILES=$(find . -name "Cargo.toml" -type f) 22 | 23 | # Counter for updated files 24 | UPDATED=0 25 | 26 | for CARGO_FILE in $CARGO_FILES; do 27 | # Skip the workspace Cargo.toml if it exists 28 | if grep -q '^\[workspace\]' "$CARGO_FILE"; then 29 | echo "Skipping workspace file: $CARGO_FILE" 30 | continue 31 | fi 32 | 33 | # Check if this is a package Cargo.toml (has [package] section) 34 | if grep -q '^\[package\]' "$CARGO_FILE"; then 35 | # Update the version using sed 36 | # This replaces the line starting with "version = " in the [package] section 37 | if sed -i "/^\[package\]/,/^\[.*\]/ s/^version = .*/version = \"$NEW_VERSION\"/" "$CARGO_FILE"; then 38 | echo "Updated: $CARGO_FILE" 39 | UPDATED=$((UPDATED + 1)) 40 | else 41 | echo "Failed to update: $CARGO_FILE" 42 | fi 43 | else 44 | echo "Skipping non-package file: $CARGO_FILE" 45 | fi 46 | done 47 | 48 | echo "Updated $UPDATED Cargo.toml files to version $NEW_VERSION" 49 | 50 | # We're not updating internal dependencies to avoid breaking path references 51 | echo "Skipping internal dependency updates to preserve path references" 52 | 53 | # The script now only updates the version field in each crate's Cargo.toml 54 | # and does not modify dependency specifications between sibling crates 55 | 56 | echo "Version update complete!" 57 | --------------------------------------------------------------------------------