text:
package org.vaadin.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.openqa.selenium.Keys;

import com.vaadin.flow.component.button.testbench.ButtonElement;
import com.vaadin.flow.component.html.testbench.ParagraphElement;
import com.vaadin.flow.component.textfield.testbench.TextFieldElement;
import com.vaadin.testbench.BrowserTest;
import com.vaadin.testbench.BrowserTestBase;

public class MainViewIT extends BrowserTestBase {

    /**
     * If running on CI, get the host name from environment variable HOSTNAME
     *
     * @return the host name
     */
    private static String getDeploymentHostname() {
        String hostname = System.getenv("HOSTNAME");
        if (hostname != null && !hostname.isEmpty()) {
            return hostname;
        }
        return "localhost";
    }

    @BeforeEach
    public void open() {
        getDriver().get("http://"+getDeploymentHostname()+":8080/");
    }

    @BrowserTest
    public void clickingButtonShowsNotification() {
        Assertions.assertFalse($(ParagraphElement.class).exists());
        $(ButtonElement.class).waitForFirst().click();
        Assertions.assertTrue($(ParagraphElement.class).exists());
    }

    @BrowserTest
    public void clickingButtonTwiceShowsTwoNotifications() {
        Assertions.assertFalse($(ParagraphElement.class).exists());
        ButtonElement button = $(ButtonElement.class).waitForFirst();
        button.click();
        button.click();
        $(ParagraphElement.class).waitForFirst();
        Assertions.assertEquals(2, $(ParagraphElement.class).all().size());
    }

    @BrowserTest
    public void testClickButtonShowsHelloAnonymousUserNotificationWhenUserNameIsEmpty() {
        ButtonElement button = $(ButtonElement.class).waitForFirst();
        button.click();
        ParagraphElement msg = $(ParagraphElement.class).waitForFirst();
        Assertions.assertEquals("Hello anonymous user", msg.getText());
    }

    @BrowserTest
    public void testClickButtonShowsHelloUserNotificationWhenUserIsNotEmpty() {
        TextFieldElement textField = $(TextFieldElement.class).waitForFirst();
        textField.setValue("Vaadiner");
        $(ButtonElement.class).waitForFirst().click();
        ParagraphElement msg = $(ParagraphElement.class).waitForFirst();
        Assertions.assertEquals("Hello Vaadiner", msg.getText());
    }

    @BrowserTest
    public void testEnterShowsHelloUserNotificationWhenUserIsNotEmpty() {
        TextFieldElement textField = $(TextFieldElement.class).waitForFirst();
        textField.setValue("Vaadiner");
        textField.sendKeys(Keys.ENTER);
        ParagraphElement msg = $(ParagraphElement.class).waitForFirst();
        Assertions.assertEquals("Hello Vaadiner", msg.getText());
    }
}


text:
server.port=${PORT:8080}
logging.level.org.atmosphere = warn
spring.mustache.check-template-location = false

# Launch the default browser when starting the application in development mode
vaadin.launch-browser=true

repo_name: vaadin/skeleton-starter-flow-spring

repo_language: Java

file_name: README

mime_type: text/plain


text:
package org.vaadin.example;

import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * The entry point of the Spring Boot application.
 *
 * Use the @PWA annotation make the application installable on phones, tablets
 * and some desktop browsers.
 *
 */
@SpringBootApplication
@PWA(name = "Project Base for Vaadin with Spring", shortName = "Project Base")
@Theme("my-theme")
public class Application implements AppShellConfigurator {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}


text:
package org.vaadin.example;

import org.springframework.beans.factory.annotation.Autowired;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.Route;

/**
 * A sample Vaadin view class.
 *

 * To implement a Vaadin view just extend any Vaadin component and use @Route
 * annotation to announce it in a URL as a Spring managed bean.
 *

 * A new instance of this class is created for every new user and every browser
 * tab/window.
 *

 * The main view contains a text field for getting the user name and a button
 * that shows a greeting message in a notification.
 */
@Route
public class MainView extends VerticalLayout {

    /**
     * Construct a new Vaadin view.
     *

     * Build the initial UI state for the user accessing the application.
     *
     * @param service
     *            The message service. Automatically injected Spring managed
     *            bean.
     */
    public MainView(@Autowired GreetService service) {

        // Use TextField for standard text input
        TextField textField = new TextField("Your name");
        textField.addClassName("bordered");

        // Button click listeners can be defined as lambda expressions
        Button button = new Button("Say hello", e -> {
            add(new Paragraph(service.greet(textField.getValue())));
        });

        // Theme variants give you predefined extra styles for components.
        // Example: Primary button has a more prominent look.
        button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        // You can specify keyboard shortcuts for buttons.
        // Example: Pressing enter in this view clicks the Button.
        button.addClickShortcut(Key.ENTER);

        // Use custom CSS classes to apply styling. This is defined in
        // styles.css.
        addClassName("centered-content");

        add(textField, button);
    }

}


text:
package org.vaadin.example;

import java.io.Serializable;

import org.springframework.stereotype.Service;

@Service
public class GreetService implements Serializable {

    public String greet(String name) {
        if (name == null || name.isEmpty()) {
            return "Hello anonymous user";
        } else {
            return "Hello " + name;
        }
    }

}


text:
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Launch Application",
      "request": "launch",
      "mainClass": "org.vaadin.example.Application"
    },
    {
      "type": "java",
      "request": "attach",
      "name": "Attach to Application started using Maven",
      "hostName": "localhost",
      "port": 5005
    }
  ]
}


text:
<!DOCTYPE html>

  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>###Project Name###</title>


text:
/*
   CSS styling examples for the Vaadin app.

   Visit https://vaadin.com/docs/styling/application-theme/ for more information.
*/

/* Example: CSS class name to center align the content . */
.centered-content {
  margin: 0 auto;
  max-width: 250px;
}

/* Example: the style is applied only to the textfields which have the `bordered` class name. */
vaadin-text-field.bordered::part(input-field) {
  box-shadow: inset 0 0 0 1px var(--lumo-contrast-30pct);
  background-color: var(--lumo-base-color);
}


text:
Place your UI views in this directory.

You can create them visually with Vaadin Designer or by hand in your IDE.


text:
local x = 25
local y = 25

local friction = 0.85

local acc = 2

local velX = 0
local velY = 0

local dir = 1

local clearScreen = true

function loadGame()
	bee = love.graphics.newImage("images/bee.png")

	frames = {}
	local frameW = 35
	local frameH = 35

	for i=0, 2 do
		table.insert(frames, love.graphics.newQuad(i * frameW, 0, frameW, frameH, 105, 35))
	end

	currentFrame = 1
end

function updateGame(dt)

	if love.keyboard.isDown("up") then
		velY = velY - acc
	elseif love.keyboard.isDown("down") then
		velY = velY + acc
	elseif love.keyboard.isDown("left") then
		velX = velX - acc
	elseif love.keyboard.isDown("right") then
		velX = velX + acc
	end

	velX = velX * friction
	velY = velY * friction

	if (x > 385 and velX > 0) or (x < 15 and velX < 0) then
		velX = velX * -0.8
	end

	if (y > 225 and velY > 0) or (y < 15 and velY < 0) then
		velY = velY * -0.8
	end

	x = x + velX
	y = y + velY
	currentFrame = currentFrame + 20 * dt

	if velX < 0 then
		dir = -1
	else
		dir = 1
	end

	if currentFrame > 4 then
		currentFrame = 1
	end

end

function keypressed(key, code)
	if key=="a" then
		clearScreen = not clearScreen
	end
end

function drawGame()
	if clearScreen then
		love.graphics.clear(WHITE)
	end
	love.graphics.draw(bee, frames[math.floor(currentFrame)], math.floor(x-(17* dir)), math.floor(y-17), 0,dir, 1)
end

repo_name: cadin/love-playdate-emulation

repo_language: Lua


text:
-- global pd color values
BLACK = {0.193, 0.184, 0.158}
WHITE = {0.747, 0.757, 0.743}
PURPLE = {0.461, 0, 0.967}

local SCREENW = 400
local SCREENH = 240
local DEVICE_SCALE_1x = 1.18

local canvas
local deviceImg
local deviceScale = 1.18
local scaleDeviceView = false

lcdShader = love.graphics.newShader[[
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
	vec4 pixel = Texel(texture, texture_coords );
	if(pixel.r < 0.65) {
		return vec4(0.193, 0.184, 0.158, pixel.a);
	} else {
		return vec4(0.747, 0.757, 0.743, pixel.a);
	}
}
]]

function setPlaydateWindow(useDevice, scale)
	love.graphics.setLineStyle('rough')
	if useDevice then
		playdateDevice(scale)
	else
		playdateWindow(scale)
	end
end

function toggleDeviceViewScaling()
	scaleDeviceView = not scaleDeviceView
end

function setDeviceScale(scale)
	if scaleDeviceView then
		deviceScale = DEVICE_SCALE_1x * scale
	else
		deviceScale = DEVICE_SCALE_1x
	end
end

function playdateDevice(scale)
	setDeviceScale(scale)

	love.graphics.setBackgroundColor(PURPLE)
	deviceImg = love.graphics.newImage("images/device-wShadow.png")
	love.window.setMode(600 * deviceScale, 509 * deviceScale, { highdpi=false})
	if scaleDeviceView then
		love.window.setTitle("Playdate @" .. scale .. "x")
	else
		love.window.setTitle("Playdate")
	end
end

function playdateWindow(scale)
	local s = scale or 1
	love.window.setMode(SCREENW * s, SCREENH * s)
	love.window.setTitle("Playdate Screen @" .. scale .. "x")
end

function createPlaydateCanvas()
	canvas = love.graphics.newCanvas(SCREENW, SCREENH)
	canvas:setFilter("nearest", "nearest", 1)
	return canvas
end

function playdateDraw(useDevice, scale)
	if useDevice then
		local canvasScale  = 1
		if scaleDeviceView then canvasScale = scale end
		love.graphics.draw(deviceImg, 0, 0, 0, 0.5 * deviceScale)
		love.graphics.setShader(lcdShader)
		love.graphics.draw(canvas, 66 * canvasScale, 64 * canvasScale, 0, canvasScale)
	else
		love.graphics.setShader(lcdShader)
		love.graphics.draw(canvas, 0, 0, 0, scale, scale)
	end
	love.graphics.setShader()
end


text:
# Playdate SDK Now Available!
The official [Playdate SDK](https://play.date/dev/) is now publicly available.

👆 **Use that instead of this**. 👆

This project is no longer active and will be archived.

## love-playdate-emulation

A basic template for previewing games built with [LÖVE](https://love2d.org) in a [Playdate](https://play.date)-like environment.

![Preview](preview.png)

This template creates an environment for running LÖVE games in a way that resembles how they might appear on the upcoming Playdate console. It creates a [Canvas](https://love2d.org/wiki/Canvas) on which to build your game, already set up as a 400x240 1-bit aliased grayscale environment. The Canvas runs a shader that converts all images to grey-on-grey. This way you can prepare black and white images for an actual Playdate game, and have them display here similar to how they might appear on device.

When running your game, choose to view it as a 1x or 2x Playdate-sized screen, or in context of the console itself.

⚠ **This is an unofficial project, for experimentation only.**
This is not an actual Playdate emulator or simulator. I have no access to the actual Playdate SDK. Games made with this template will **not** run on the device when it's released.

## Getting Started

### Requirements
1. [LÖVE](https://love2d.org)

### Setup
Clone this repo, and start building your game in the `game.lua` file.

This file has `loadGame`, `updateGame`, and `drawGame` functions that corresond to LÖVE's `load`, `update`, and `draw`.

### Usage
Your game should be built in the `game.lua` file. `main.lua` handles the basic setup and wraps your game in the simulated Playdate environment. `playdate-env.lua` contains the code for emulating the Playdate screen.

When running your game, press keys `1` through `4` to view the screen display at 1x, 2x, 3x or 4x scale.
Press `d` to toggle the device image.
Press `Shift-D` to toggle scaling on the device image.

## License
This project is licensed under the Unlicense - see the [LICENSE](LICENSE) file for details.

All Playdate imagery and trademarks are property of [Panic, Inc](https://panic.com). No copyright intended 😬.


text:
require("playdate-env")
require("game")

local SCALE = 2
local useDevice = false
local canvas

function love.load()
	setPlaydateWindow(useDevice, SCALE)
	canvas = createPlaydateCanvas(WHITE)
	loadGame(canvas)
end

function love.update(dt)
	updateGame(dt)
end

function love.keypressed(key, code)
	if key == "d" then
		if love.keyboard.isDown("lshift") or love.keyboard.isDown("rshift") then
			toggleDeviceViewScaling()
		else
			useDevice = not useDevice
		end
		setPlaydateWindow(useDevice, SCALE)
	elseif key=="1" then
		SCALE = 1
		setPlaydateWindow(useDevice, SCALE)
	elseif key=="2" then
		SCALE = 2
		setPlaydateWindow(useDevice, SCALE)
	elseif key=="3" then
		SCALE = 3
		setPlaydateWindow(useDevice, SCALE)
	elseif key=="4" then
		SCALE = 4
		setPlaydateWindow(useDevice, SCALE)
	end

	keypressed(key, code)
end

function love.draw()
	love.graphics.setCanvas(canvas)
		drawGame()
	love.graphics.setCanvas()

	love.graphics.setColor(1, 1, 1, 1)
	playdateDraw(useDevice, SCALE)
end


text:
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.7] - 2023-01-16
### Added
- Support for Schnorr signature creation and validation
- Fixed bug which would not correctly parse a BOLT11 invoice with amount explicitly set to 0.
- Bump Elixir & Erlang Requirements & dependencies

## [0.1.4] - 2021-05-19
### Added
- BIP32 support with new modules for extended keys and derivation paths.
- Security document for vulnerability reports.
- Extra test for PSBT with 0 inputs and 0 outputs.

### Changed
- Jason updated to 1.2.2

## [0.1.3] - 2021-04-19
### Added
- Disclaimer to README.
- Support for Bech32m.
- Private key module with signing functionality.
- hash160 added to utils.

### Changed
- Decimal dependency.

## [0.1.2] - 2021-01-13
### Added
- Code snippet examples to README.
- Padding function to utils.

### Fixed
- Padding to public keys and transaction IDs.

## [0.1.1] - 2020-12-21
### Added
- Native Elixir Secp256k1 elliptic curve support with ECDSA public key recovery.

### Removed
- libsecp25k1 and ex_doc dependencies.

## [0.1.0] - 2020-12-02
### Added
- Bech32 and base58 encoding.
- Address and lightning invoice serialization.
- PSBT serialization.
- Transaction module.

[0.1.4]: https://diff.hex.pm/diff/bitcoinex/0.1.3..0.1.4
[0.1.3]: https://diff.hex.pm/diff/bitcoinex/0.1.2..0.1.3
[0.1.2]: https://diff.hex.pm/diff/bitcoinex/0.1.1..0.1.2
[0.1.1]: https://diff.hex.pm/diff/bitcoinex/0.1.0..0.1.1
[0.1.0]: https://preview.hex.pm/preview/bitcoinex/0.1.0

repo_name: RiverFinancial/bitcoinex

repo_language: Elixir


text:
# Security Policy

## Supported Versions

n/a

## Reporting a Vulnerability

Report security issues to security@river.com.

The following keys may be used to communicate sensitive information to developers:

| Name | Fingerprint |
|------|-------------|
| Philip Glazman | 1DED 561A 828F 6542 9FA9  7B08 C31B DE21 A1C0 170D |
| bruteforcecat | 741C B8EC 98F6 8979 1125  0488 5FEF C5A8 B4A2 E122 |
| Sachin Meier | 9AA7 515C 4818 C98C 0792  F074 C599 15FE 4B6D 6276 |


text:
defmodule Bitcoinex.MixProject do
  use Mix.Project

  def project do
    [
      app: :bitcoinex,
      version: "0.1.7",
      elixir: "~> 1.11",
      package: package(),
      start_permanent: Mix.env() == :prod,
      dialyzer: dialyzer(),
      deps: deps(),
      aliases: aliases(),
      description: description(),
      source_url: "https://github.com/RiverFinancial/bitcoinex"
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:credo, "~> 1.6", only: [:dev, :test], runtime: false},
      {:dialyxir, "~> 1.2.0", only: [:dev, :test], runtime: false},
      {:excoveralls, "~> 0.10", only: :test},
      {:mix_test_watch, "~> 1.1", only: :dev, runtime: false},
      {:stream_data, "~> 0.1", only: :test},
      {:timex, "~> 3.1"},
      {:decimal, "~> 1.0 or ~> 2.0"},
      {:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
      {:benchee, ">= 1.0.0", only: :dev}
    ]
  end

  defp aliases do
    [
      "lint.all": [
        "format --check-formatted",
        "credo --strict --only warning",
        "dialyzer --halt-exit-status"
      ],
      compile: ["compile --warnings-as-errors"]
    ]
  end

  # Dialyzer configuration
  defp dialyzer do
    [
      plt_file: plt_file(),
      flags: [
        :error_handling
      ],
      ignore_warnings: ".dialyzer_ignore.exs"
    ]
  end

  # Use a custom PLT directory for CI caching.
  defp plt_file do
    {:no_warn, "_plts/dialyzer.plt"}
  end

  defp package do
    [
      files: ~w(lib test .formatter.exs mix.exs README.md UNLICENSE CHANGELOG.md SECURITY.md),
      licenses: ["Unlicense"],
      links: %{"GitHub" => "https://github.com/RiverFinancial/bitcoinex"}
    ]
  end

  defp description() do
    "Bitcoinex is a Bitcoin Library for Elixir."
  end
end


text:
![Bitcoinex](https://user-images.githubusercontent.com/8378656/102842648-4f671380-43bc-11eb-8e0d-72c2a107e5ed.png)
# Bitcoinex

Bitcoinex is striving to be the best and most up-to-date Bitcoin Library for Elixir.

## Documentation
Documentation is available on [hexdocs.pm](https://hexdocs.pm/bitcoinex/api-reference.html).

## Current Utilities
* Serialization and validation for Bech32 and Base58.
* Support for standard on-chain scripts (P2PKH..P2WPKH) and Bolt#11 Lightning Invoices.
* Transaction serialization.
* Basic PSBT (BIP174) parsing.

## Usage

With [Hex](https://hex.pm/packages/bitcoinex):

    {:bitcoinex, "~> 0.1.7"}

Local:

    $ mix deps.get
    $ mix compile

## Examples

Decode a Lightning Network invoice:

  ```elixir
  Bitcoinex.LightningNetwork.decode_invoice("lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp")

  {:ok,
    %Bitcoinex.LightningNetwork.Invoice{
      amount_msat: 250000000,
      description: "1 cup coffee",
      description_hash: nil,
      destination: "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad",
      expiry: 60,
      fallback_address: nil,
      min_final_cltv_expiry: 18,
      network: :mainnet,
      payment_hash: "0001020304050607080900010203040506070809000102030405060708090102",
      route_hints: [],
      timestamp: 1496314658
    }}
  ```

Parse a BIP-174 Partially Signed Bitcoin Transaction:

  ```elixir
  Bitcoinex.PSBT.decode("cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=")

  {:ok,
    %Bitcoinex.PSBT{
     global: %Bitcoinex.PSBT.Global{
       proprietary: nil,
       unsigned_tx: %Bitcoinex.Transaction{...},
     inputs: [
       %Bitcoinex.PSBT.In{
         bip32_derivation: [
           %{
             derivation: "b4a6ba67000000800000008004000080",
             public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46"
           },
           %{
             derivation: "b4a6ba67000000800000008005000080",
             public_key: "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd"
           }
         ],
         final_scriptsig: nil,
         final_scriptwitness: nil,
         non_witness_utxo: nil,
         partial_sig: %{
           public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",
           signature: "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01"
         },
         por_commitment: nil,
         proprietary: nil,
         redeem_script: "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681",
         sighash_type: nil,
         witness_script: "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae",
         witness_utxo: %Bitcoinex.Transaction.Out{
           script_pub_key: "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87",
           value: 199909013
         }
       }
     ],
     outputs: []
    }}
 ```

Handle bitcoin addresses:

  ```elixir
  {:ok, {:mainnet, witness_version, witness_program}} = Bitcoinex.Segwit.decode_address("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")

  Bitcoinex.Segwit.encode_address(:mainnet, witness_version, witness_program)

  {:ok, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"}
  ```

## Roadmap
Continued support for on-chain and off-chain functionality including:
* Full script support including validation.
* Block serialization.
* Transaction creation.
* Broader BIP support including BIP32.

## Contributing
We have big goals and this library is still in a very early stage. Contributions and comments are very much welcome.

## DISCLAIMER

This software is not recommended for use in handling real funds. The addresses, public keys, and signatures generated by this library are not recommended to be used for sending or receiving real funds.


text:
name: Elixir CI

on: [push, pull_request]

jobs:
  build_with_opt_24:
    name: Build and test older versions ${{matrix.elixir}}-otp-${{matrix.otp}}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        otp: ['24.0']
        elixir: ['1.12.0', '1.13.0', '1.14.0']
    steps:
    - uses: actions/checkout@v2
    - name: Set up Elixir
      uses: erlef/setup-elixir@v1
      with:
        otp-version: ${{matrix.otp}}
        elixir-version: ${{matrix.elixir}}
    - name: Restore dependencies cache
      uses: actions/cache@v2
      with:
        path: deps
        key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
        restore-keys: ${{ runner.os }}-mix-
    - name: Install dependencies
      run: mix deps.get
    - name: Run tests
      run: mix test
    - name: format and lint
      run: mix lint.all

  build_with_opt_25:
    name: Build and test ${{matrix.elixir}}-otp-${{matrix.otp}}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        otp: ['25.0']
        elixir: ['1.13.0', '1.14.0']
    steps:
    - uses: actions/checkout@v2
    - name: Set up Elixir
      uses: erlef/setup-elixir@v1
      with:
        otp-version: ${{matrix.otp}}
        elixir-version: ${{matrix.elixir}}
    - name: Restore dependencies cache
      uses: actions/cache@v2
      with:
        path: deps
        key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
        restore-keys: ${{ runner.os }}-mix-
    - name: Install dependencies
      run: mix deps.get
    - name: Run tests
      run: mix test
    - name: format and lint
      run: mix lint.all


text:
defmodule Bitcoinex.Base58 do
  @moduledoc """
    Includes Base58 serialization and validation.

    Some code is inspired by:
    https://github.com/comboy/bitcoin-elixir/blob/develop/lib/bitcoin/base58_check.ex
  """
  alias Bitcoinex.Utils

  @typedoc """
    Base58 encoding is only supported for p2sh and p2pkh address types.
  """
  @type address_type :: :p2sh | :p2pkh
  @base58_encode_list ~c(123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz)
  @base58_decode_map @base58_encode_list |> Enum.with_index() |> Enum.into(%{})

  @base58_0 <<?1>>
  @type byte_list :: list(byte())

  @doc """
    Decodes a Base58 encoded string into a byte array and validates checksum.
  """
  @spec decode(binary) :: {:ok, binary} | {:error, atom}
  def decode(binary)

  def decode(""), do: {:ok, ""}

  def decode(<<body_and_checksum::binary>>) do
    if valid_charset?(body_and_checksum) do
      body_and_checksum
      |> decode_base!()
      |> validate_checksum()
    else
      {:error, :invalid_characters}
    end
  end

  @doc """
    Decodes a Base58 encoded string into a byte array.
  """
  @spec decode_base!(binary) :: binary
  def decode_base!(binary)

  def decode_base!(@base58_0), do: <<0>>

  def decode_base!(@base58_0 <> body) when byte_size(body) > 0 do
    decode_base!(@base58_0) <> decode_base!(body)
  end

  def decode_base!(""), do: ""

  def decode_base!(bin) do
    bin
    |> :binary.bin_to_list()
    |> Enum.map(&Map.fetch!(@base58_decode_map, &1))
    |> Integer.undigits(58)
    |> :binary.encode_unsigned()
  end

  @doc """
    Validates a Base58 checksum.
  """
  @spec validate_checksum(binary) :: {:ok, binary} | {:error, atom}
  def validate_checksum(data) do
    [decoded_body, checksum] =
      data
      |> :binary.bin_to_list()
      |> Enum.split(-4)
      |> Tuple.to_list()
      |> Enum.map(&:binary.list_to_bin(&1))

    if checksum == checksum(decoded_body) do
      {:ok, decoded_body}
    else
      {:error, :invalid_checksum}
    end
  end

  defp valid_charset?(""), do: true

  defp valid_charset?(<<char>> <> string),
    do: char in @base58_encode_list && valid_charset?(string)

  @doc """
    Encodes binary into a Base58 encoded string.
  """
  @spec encode(binary) :: String.t()
  def encode(bin) do
    bin
    |> append_checksum()
    |> encode_base()
  end

  @spec encode_base(binary) :: String.t()
  def encode_base(binary)

  def encode_base(""), do: ""

  def encode_base(<<0>> <> tail) do
    @base58_0 <> encode_base(tail)
  end

  @doc """
    Encodes a binary into a Base58 encoded string.
  """
  def encode_base(bin) do
    bin
    |> :binary.decode_unsigned()
    |> Integer.digits(58)
    |> Enum.map(&Enum.fetch!(@base58_encode_list, &1))
    |> List.to_string()
  end

  @spec append_checksum(binary) :: binary
  def append_checksum(body) do
    body <> checksum(body)
  end

  @spec checksum(binary) :: binary
  defp checksum(body) do
    <<checksum::binary-4, _rest::binary>> = Utils.double_sha256(body)

    checksum
  end
end


text:
defmodule Bitcoinex.Opcode do
  @moduledoc """
  	a module for storing the list of opcodes by number and atom
  	and defining the functions associated with each opcode
  """

  def opcode_nums,
    do: %{
      0x00 => :op_0,
      0x4C => :op_pushdata1,
      0x4D => :op_pushdata2,
      0x4E => :op_pushdata4,
      0x4F => :op_1negate,
      0x50 => :op_reserved,
      0x51 => :op_1,
      0x52 => :op_2,
      0x53 => :op_3,
      0x54 => :op_4,
      0x55 => :op_5,
      0x56 => :op_6,
      0x57 => :op_7,
      0x58 => :op_8,
      0x59 => :op_9,
      0x5A => :op_10,
      0x5B => :op_11,
      0x5C => :op_12,
      0x5D => :op_13,
      0x5E => :op_14,
      0x5F => :op_15,
      0x60 => :op_16,
      0x61 => :op_nop,
      0x62 => :op_ver,
      0x63 => :op_if,
      0x64 => :op_notif,
      0x65 => :op_verif,
      0x66 => :op_vernotif,
      0x67 => :op_else,
      0x68 => :op_endif,
      0x69 => :op_verify,
      0x6A => :op_return,
      0x6B => :op_toaltstack,
      0x6C => :op_fromaltstack,
      0x6D => :op_2drop,
      0x6E => :op_2dup,
      0x6F => :op_3dup,
      0x70 => :op_2over,
      0x71 => :op_2rot,
      0x72 => :op_2swap,
      0x73 => :op_ifdup,
      0x74 => :op_depth,
      0x75 => :op_drop,
      0x76 => :op_dup,
      0x77 => :op_nip,
      0x78 => :op_over,
      0x79 => :op_pick,
      0x7A => :op_roll,
      0x7B => :op_rot,
      0x7C => :op_swap,
      0x7D => :op_tuck,
      0x7E => :op_cat,
      0x7F => :op_substr,
      0x80 => :op_left,
      0x81 => :op_right,
      0x82 => :op_size,
      0x83 => :op_invert,
      0x84 => :op_and,
      0x85 => :op_or,
      0x86 => :op_xor,
      0x87 => :op_equal,
      0x88 => :op_equalverify,
      0x89 => :op_reserved1,
      0x8A => :op_reserved2,
      0x8B => :op_1add,
      0x8C => :op_1sub,
      0x8D => :op_2mul,
      0x8E => :op_2div,
      0x8F => :op_negate,
      0x90 => :op_abs,
      0x91 => :op_not,
      0x92 => :op_0notequal,
      0x93 => :op_add,
      0x94 => :op_sub,
      0x95 => :op_mul,
      0x96 => :op_div,
      0x97 => :op_mod,
      0x98 => :op_lshift,
      0x99 => :op_rshift,
      0x9A => :op_booland,
      0x9B => :op_boolor,
      0x9C => :op_numequal,
      0x9D => :op_numequalverify,
      0x9E => :op_numnotequal,
      0x9F => :op_lessthan,
      0xA0 => :op_greaterthan,
      0xA1 => :op_lessthanorequal,
      0xA2 => :op_greaterthanorequal,
      0xA3 => :op_min,
      0xA4 => :op_max,
      0xA5 => :op_within,
      0xA6 => :op_ripemd160,
      0xA7 => :op_sha1,
      0xA8 => :op_sha256,
      0xA9 => :op_hash160,
      0xAA => :op_hash256,
      0xAB => :op_codeseparator,
      0xAC => :op_checksig,
      0xAD => :op_checksigverify,
      0xAE => :op_checkmultisig,
      0xAF => :op_checkmultisigverify,
      0xB0 => :op_nop1,
      0xB1 => :op_checklocktimeverify,
      0xB2 => :op_nop3,
      0xB3 => :op_nop4,
      0xB4 => :op_nop5,
      0xB5 => :op_nop6,
      0xB6 => :op_nop7,
      0xB7 => :op_nop8,
      0xB8 => :op_nop9,
      0xB9 => :op_nop10,
      0xFA => :op_smallinteger,
      0xFB => :op_pubkeys,
      0xFD => :op_pubkeyhash,
      0xFE => :op_pubkey,
      0xFF => :op_invalidopcode
    }

  def opcode_atoms,
    do: %{
      op_0: 0x00,
      op_false: 0x00,
      op_pushdata1: 0x4C,
      op_pushdata2: 0x4D,
      op_pushdata4: 0x4E,
      op_1negate: 0x4F,
      op_reserved: 0x50,
      op_1: 0x51,
      op_true: 0x51,
      op_2: 0x52,
      op_3: 0x53,
      op_4: 0x54,
      op_5: 0x55,
      op_6: 0x56,
      op_7: 0x57,
      op_8: 0x58,
      op_9: 0x59,
      op_10: 0x5A,
      op_11: 0x5B,
      op_12: 0x5C,
      op_13: 0x5D,
      op_14: 0x5E,
      op_15: 0x5F,
      op_16: 0x60,
      op_nop: 0x61,
      op_ver: 0x62,
      op_if: 0x63,
      op_notif: 0x64,
      op_verif: 0x65,
      op_vernotif: 0x66,
      op_else: 0x67,
      op_endif: 0x68,
      op_verify: 0x69,
      op_return: 0x6A,
      op_toaltstack: 0x6B,
      op_fromaltstack: 0x6C,
      op_2drop: 0x6D,
      op_2dup: 0x6E,
      op_3dup: 0x6F,
      op_2over: 0x70,
      op_2rot: 0x71,
      op_2swap: 0x72,
      op_ifdup: 0x73,
      op_depth: 0x74,
      op_drop: 0x75,
      op_dup: 0x76,
      op_nip: 0x77,
      op_over: 0x78,
      op_pick: 0x79,
      op_roll: 0x7A,
      op_rot: 0x7B,
      op_swap: 0x7C,
      op_tuck: 0x7D,
      op_cat: 0x7E,
      op_substr: 0x7F,
      op_left: 0x80,
      op_right: 0x81,
      op_size: 0x82,
      op_invert: 0x83,
      op_and: 0x84,
      op_or: 0x85,
      op_xor: 0x86,
      op_equal: 0x87,
      op_equalverify: 0x88,
      op_reserved1: 0x89,
      op_reserved2: 0x8A,
      op_1add: 0x8B,
      op_1sub: 0x8C,
      op_2mul: 0x8D,
      op_2div: 0x8E,
      op_negate: 0x8F,
      op_abs: 0x90,
      op_not: 0x91,
      op_0notequal: 0x92,
      op_add: 0x93,
      op_sub: 0x94,
      op_mul: 0x95,
      op_div: 0x96,
      op_mod: 0x97,
      op_lshift: 0x98,
      op_rshift: 0x99,
      op_booland: 0x9A,
      op_boolor: 0x9B,
      op_numequal: 0x9C,
      op_numequalverify: 0x9D,
      op_numnotequal: 0x9E,
      op_lessthan: 0x9F,
      op_greaterthan: 0xA0,
      op_lessthanorequal: 0xA1,
      op_greaterthanorequal: 0xA2,
      op_min: 0xA3,
      op_max: 0xA4,
      op_within: 0xA5,
      op_ripemd160: 0xA6,
      op_sha1: 0xA7,
      op_sha256: 0xA8,
      op_hash160: 0xA9,
      op_hash256: 0xAA,
      op_codeseparator: 0xAB,
      op_checksig: 0xAC,
      op_checksigverify: 0xAD,
      op_checkmultisig: 0xAE,
      op_checkmultisigverify: 0xAF,
      op_nop1: 0xB0,
      op_nop2: 0xB1,
      op_checklocktimeverify: 0xB1,
      op_nop3: 0xB2,
      op_nop4: 0xB3,
      op_nop5: 0xB4,
      op_nop6: 0xB5,
      op_nop7: 0xB6,
      op_nop8: 0xB7,
      op_nop9: 0xB8,
      op_nop10: 0xB9,
      op_smallinteger: 0xFA,
      op_pubkeys: 0xFB,
      op_pubkeyhash: 0xFD,
      op_pubkey: 0xFE,
      op_invalidopcode: 0xFF
    }
end


text:
defmodule Bitcoinex do
  @moduledoc """
  Documentation for Bitcoinex.

  Bitcoinex is an Elixir library supporting basic Bitcoin functionality.
  """
end


text:
defmodule Bitcoinex.Bech32 do
  @moduledoc """
  Includes Bech32 serialization and validation.

  Reference: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
  """

  import Bitwise

  @gen [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3]
  @data_charset_list 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
  @data_charset_map @data_charset_list
                    |> Enum.zip(0..Enum.count(@data_charset_list))
                    |> Enum.into(%{})
  @hrp_char_code_point_upper_limit 126
  @hrp_char_code_point_lower_limit 33
  @max_overall_encoded_length 90
  @separator "1"

  @encoding_constant_map %{
    bech32: 1,
    bech32m: 0x2BC830A3
  }

  @type encoding_type :: :bech32 | :bech32m
  @type hrp :: String.t()
  @type data :: list(integer)

  @type witness_version :: Range.t(0, 16)
  @type witness_program :: list(integer)
  @type max_encoded_length :: pos_integer() | :infinity

  @type error :: atom()

  # Inspired by Ecto.Changeset. more descriptive than result tuple
  defmodule DecodeResult do
    @type t() :: %__MODULE__{
            encoded_str: String.t(),
            encoding_type: Bitcoinex.Bech32.encoding_type() | nil,
            hrp: String.t() | nil,
            data: String.t() | nil,
            error: atom() | nil
          }
    defstruct [:encoded_str, :encoding_type, :hrp, :data, :error]

    @spec add_error(t(), atom()) :: t()
    def add_error(%DecodeResult{} = decode_result, error) do
      %{
        decode_result
        | error: error
      }
    end

    @doc """
    This naming is taken from Haskell. we will treat DecodeResult a bit like an Monad
    And bind function will take a function that take DecodeResult that's only without error and return DecodeResult
    And we can skip handling same error case for all function
    """
    @spec bind(t(), (t -> t())) :: t()
    def bind(%DecodeResult{error: error} = decode_result, _fun) when not is_nil(error) do
      decode_result
    end

    def bind(%DecodeResult{} = decode_result, fun) do
      fun.(decode_result)
    end
  end

  @spec decode(String.t(), max_encoded_length()) ::
          {:ok, {encoding_type, hrp, data}} | {:error, error}
  def decode(bech32_str, max_encoded_length \\ @max_overall_encoded_length)
      when is_binary(bech32_str) do
    %DecodeResult{
      encoded_str: bech32_str
    }
    |> DecodeResult.bind(&validate_bech32_length(&1, max_encoded_length))
    |> DecodeResult.bind(&validate_bech32_case/1)
    |> DecodeResult.bind(&split_bech32_str/1)
    |> DecodeResult.bind(&validate_checksum_and_add_encoding_type/1)
    |> format_bech32_decoding_result
  end

  @spec encode(hrp, data | String.t(), encoding_type, max_encoded_length()) ::
          {:ok, String.t()} | {:error, error}
  def encode(hrp, data, encoding_type, max_encoded_length \\ @max_overall_encoded_length)

  def encode(hrp, data, encoding_type, max_encoded_length) when is_list(data) do
    hrp_charlist = hrp |> String.to_charlist()

    if is_valid_hrp?(hrp_charlist) do
      checksummed = data ++ create_checksum(hrp_charlist, data, encoding_type)
      dp = Enum.map(checksummed, &Enum.at(@data_charset_list, &1)) |> List.to_string()
      encoded_result = <<hrp::binary, @separator, dp::binary>>

      case validate_bech32_length(encoded_result, max_encoded_length) do
        :ok ->
          {:ok, String.downcase(encoded_result)}

        {:error, error} ->
          {:error, error}
      end
    else
      {:error, :hrp_char_out_opf_range}
    end
  end

  # Here we assume caller pass raw ASCII string
  def encode(hrp, data, encoding_type, max_encoded_length) when is_binary(data) do
    data_integers = data |> String.to_charlist() |> Enum.map(&Map.get(@data_charset_map, &1))

    case check_data_charlist_validity(data_integers) do
      :ok ->
        encode(hrp, data_integers, encoding_type, max_encoded_length)

      {:error, error} ->
        {:error, error}
    end
  end

  # Big endian conversion of a list of integer from base 2^frombits to base 2^tobits.
  # ref https://github.com/sipa/bech32/blob/master/ref/python/segwit_addr.py#L80

  @spec convert_bits(list(integer), integer(), integer(), boolean()) ::
          {:error, :invalid_data} | {:ok, list(integer)}
  def convert_bits(data, from_bits, to_bits, padding \\ true) when is_list(data) do
    max_v = (1 <<< to_bits) - 1
    max_acc = (1 <<< (from_bits + to_bits - 1)) - 1

    result =
      Enum.reduce_while(data, {0, 0, []}, fn val, {acc, bits, ret} ->
        if val < 0 or val >>> from_bits != 0 do
          {:halt, {:error, :invalid_data}}
        else
          acc = (acc <<< from_bits ||| val) &&& max_acc
          bits = bits + from_bits
          {bits, ret} = convert_bits_loop(to_bits, max_v, acc, bits, ret)
          {:cont, {acc, bits, ret}}
        end
      end)

    case result do
      {acc, bits, ret} ->
        if padding && bits > 0 do
          {:ok, ret ++ [acc <<< (to_bits - bits) &&& max_v]}
        else
          if bits >= from_bits || (acc <<< (to_bits - bits) &&& max_v) > 0 do
            {:error, :invalid_data}
          else
            {:ok, ret}
          end
        end

      {:error, :invalid_data} = e ->
        e
    end
  end

  defp convert_bits_loop(to, max_v, acc, bits, ret) do
    if bits >= to do
      bits = bits - to
      ret = ret ++ [acc >>> bits &&& max_v]
      convert_bits_loop(to, max_v, acc, bits, ret)
    else
      {bits, ret}
    end
  end

  defp validate_checksum_and_add_encoding_type(
         %DecodeResult{
           data: data,
           hrp: hrp
         } = decode_result
       ) do
    case bech32_polymod(bech32_hrp_expand(hrp) ++ data) do
      unquote(@encoding_constant_map.bech32) ->
        %DecodeResult{decode_result | encoding_type: :bech32}

      unquote(@encoding_constant_map.bech32m) ->
        %DecodeResult{decode_result | encoding_type: :bech32m}

      _ ->
        DecodeResult.add_error(decode_result, :invalid_checksum)
    end
  end

  defp create_checksum(hrp, data, encoding_type) do
    values = bech32_hrp_expand(hrp) ++ data ++ [0, 0, 0, 0, 0, 0]
    mod = Bitwise.bxor(bech32_polymod(values), @encoding_constant_map[encoding_type])
    for p <- 0..5, do: mod >>> (5 * (5 - p)) &&& 31
  end

  defp bech32_polymod(values) do
    Enum.reduce(
      values,
      1,
      fn value, acc ->
        b = acc >>> 25
        acc = Bitwise.bxor((acc &&& 0x1FFFFFF) <<< 5, value)

        Enum.reduce(0..length(@gen), acc, fn i, in_acc ->
          right_side =
            if (b >>> i &&& 1) != 0 do
              Enum.at(@gen, i)
            else
              0
            end

          Bitwise.bxor(in_acc, right_side)
        end)
      end
    )
  end

  defp bech32_hrp_expand(chars) when is_list(chars) do
    Enum.map(chars, &(&1 >>> 5)) ++ [0 | Enum.map(chars, &(&1 &&& 31))]
  end

  defp format_bech32_decoding_result(%DecodeResult{
         error: nil,
         hrp: hrp,
         data: data,
         encoding_type: encoding_type
       })
       when not is_nil(hrp) and not is_nil(data) do
    {:ok, {encoding_type, to_string(hrp), Enum.drop(data, -6)}}
  end

  defp format_bech32_decoding_result(%DecodeResult{
         error: error
       }) do
    {:error, error}
  end

  defp split_bech32_str(
         %DecodeResult{
           encoded_str: encoded_str
         } = decode_result
       ) do
    # the bech 32 is at most 90 chars
    # so it's ok to do 3 time reverse here
    # otherwise we can use binary pattern matching with index for better performance
    downcase_encoded_str = encoded_str |> String.downcase()

    with {_, [data, hrp]} when hrp != "" and data != "" <-
           {:split_by_separator,
            downcase_encoded_str |> String.reverse() |> String.split(@separator, parts: 2)},
         hrp = hrp |> String.reverse() |> String.to_charlist(),
         {_, true} <- {:check_hrp_validity, is_valid_hrp?(hrp)},
         data <-
           data
           |> String.reverse()
           |> String.to_charlist()
           |> Enum.map(&Map.get(@data_charset_map, &1)),
         {_, :ok} <- {:check_data_validity, check_data_charlist_validity(data)} do
      %DecodeResult{
        decode_result
        | hrp: hrp,
          data: data
      }
    else
      {:split_by_separator, [_]} ->
        DecodeResult.add_error(decode_result, :no_separator_character)

      {:split_by_separator, ["", _]} ->
        DecodeResult.add_error(decode_result, :empty_data)

      {:split_by_separator, [_, ""]} ->
        DecodeResult.add_error(decode_result, :empty_hrp)

      {:check_hrp_validity, false} ->
        DecodeResult.add_error(decode_result, :hrp_char_out_opf_range)

      {:check_data_validity, {:error, error}} ->
        DecodeResult.add_error(decode_result, error)
    end
  end

  defp validate_bech32_length(
         %DecodeResult{
           encoded_str: encoded_str
         } = decode_result,
         max_length
       ) do
    case validate_bech32_length(encoded_str, max_length) do
      :ok ->
        decode_result

      {:error, error} ->
        DecodeResult.add_error(decode_result, error)
    end
  end

  defp validate_bech32_length(encoded_str, :infinity) when is_binary(encoded_str) do
    :ok
  end

  defp validate_bech32_length(
         encoded_str,
         max_length
       )
       when is_binary(encoded_str) and byte_size(encoded_str) > max_length do
    {:error, :overall_max_length_exceeded}
  end

  defp validate_bech32_length(
         encoded_str,
         _max_length
       )
       when is_binary(encoded_str) do
    :ok
  end

  defp validate_bech32_case(
         %DecodeResult{
           encoded_str: encoded_str
         } = decode_result
       ) do
    case String.upcase(encoded_str) == encoded_str or String.downcase(encoded_str) == encoded_str do
      true ->
        decode_result

      false ->
        DecodeResult.add_error(decode_result, :mixed_case)
    end
  end

  defp check_data_charlist_validity(charlist) do
    if length(charlist) >= 6 do
      if Enum.all?(charlist, &(!is_nil(&1))) do
        :ok
      else
        {:error, :contain_invalid_data_char}
      end
    else
      {:error, :too_short_checksum}
    end
  end

  defp is_valid_hrp?(hrp) when is_list(hrp), do: Enum.all?(hrp, &is_valid_hrp_char?/1)

  defp is_valid_hrp_char?(char) do
    char <= @hrp_char_code_point_upper_limit and char >= @hrp_char_code_point_lower_limit
  end
end


text:
defmodule Bitcoinex.Network do
  @moduledoc """
    Includes network-specific paramater options.

    Supported networks include mainnet, testnet3, and regtest.
  """

  @enforce_keys [
    :name,
    :hrp_segwit_prefix,
    :p2pkh_version_decimal_prefix,
    :p2sh_version_decimal_prefix
  ]
  defstruct [
    :name,
    :hrp_segwit_prefix,
    :p2pkh_version_decimal_prefix,
    :p2sh_version_decimal_prefix
  ]

  @type t() :: %__MODULE__{
          name: atom,
          hrp_segwit_prefix: String.t(),
          p2pkh_version_decimal_prefix: integer(),
          p2sh_version_decimal_prefix: integer()
        }

  @type network_name :: :mainnet | :testnet | :regtest

  @doc """
    Returns a list of supported networks.
  """
  def supported_networks() do
    [
      mainnet(),
      testnet(),
      regtest()
    ]
  end

  def mainnet do
    %__MODULE__{
      name: :mainnet,
      hrp_segwit_prefix: "bc",
      p2pkh_version_decimal_prefix: 0,
      p2sh_version_decimal_prefix: 5
    }
  end

  def testnet do
    %__MODULE__{
      name: :testnet,
      hrp_segwit_prefix: "tb",
      p2pkh_version_decimal_prefix: 111,
      p2sh_version_decimal_prefix: 196
    }
  end

  def regtest do
    %__MODULE__{
      name: :regtest,
      hrp_segwit_prefix: "bcrt",
      p2pkh_version_decimal_prefix: 111,
      p2sh_version_decimal_prefix: 196
    }
  end

  @spec get_network(network_name) :: t()
  def get_network(:mainnet) do
    mainnet()
  end

  def get_network(:testnet) do
    testnet()
  end

  def get_network(:regtest) do
    regtest()
  end
end


text:
defmodule Bitcoinex.Segwit do
  @moduledoc """
  SegWit address serialization.
  """

  alias Bitcoinex.Bech32

  @valid_witness_program_length_range 2..40
  @valid_witness_version 0..16
  @supported_network [:mainnet, :testnet, :regtest]

  @type hrp :: String.t()
  @type data :: list(integer)
  # seem no way to use list of atom module attribute in type spec
  @type network :: :testnet | :mainnet | :regtest

  @type witness_version :: 0..16
  @type witness_program :: list(integer)

  @type error :: atom()

  @doc """
    Decodes an address and returns its network, witness version, and witness program.
  """
  @spec decode_address(String.t()) ::
          {:ok, {network, witness_version, witness_program}} | {:error, error}
  def decode_address(address) when is_binary(address) do
    with {_, {:ok, {encoding_type, hrp, data}}} <- {:decode_bech32, Bech32.decode(address)},
         {_, {:ok, network}} <- {:parse_network, parse_network(hrp |> String.to_charlist())},
         {_, {:ok, {version, program}}} <- {:parse_segwit_data, parse_segwit_data(data)} do
      case witness_version_to_bech_encoding(version) do
        ^encoding_type ->
          {:ok, {network, version, program}}

        _ ->
          # encoding type derived from witness version (first byte of data) is different from the code derived from bech32 decoding
          {:error, :invalid_checksum}
      end
    else
      {_, {:error, error}} ->
        {:error, error}
    end
  end

  @doc """
    Encodes an address string.
  """
  @spec encode_address(network, witness_version, witness_program) ::
          {:ok, String.t()} | {:error, error}
  def encode_address(network, _, _) when network not in @supported_network do
    {:error, :invalid_network}
  end

  def encode_address(_, witness_version, _)
      when witness_version not in @valid_witness_version do
    {:error, :invalid_witness_version}
  end

  def encode_address(network, version, program) do
    with {:ok, converted_program} <- Bech32.convert_bits(program, 8, 5),
         {:is_program_length_valid, true} <-
           {:is_program_length_valid, is_program_length_valid?(version, program)} do
      hrp =
        case network do
          :mainnet ->
            "bc"

          :testnet ->
            "tb"

          :regtest ->
            "bcrt"
        end

      Bech32.encode(hrp, [version | converted_program], witness_version_to_bech_encoding(version))
    else
      {:is_program_length_valid, false} ->
        {:error, :invalid_program_length}

      error ->
        error
    end
  end

  @doc """
  Simpler Interface to check if address is valid
  """
  @spec is_valid_segswit_address?(String.t()) :: boolean
  def is_valid_segswit_address?(address) when is_binary(address) do
    case decode_address(address) do
      {:ok, _} ->
        true

      _ ->
        false
    end
  end

  @spec get_segwit_script_pubkey(witness_version, witness_program) :: String.t()
  def get_segwit_script_pubkey(version, program) do
    # OP_0 is encoded as 0x00, but OP_1 through OP_16 are encoded as 0x51 though 0x60
    wit_version_adjusted = if(version == 0, do: 0, else: version + 0x50)

    [
      wit_version_adjusted,
      Enum.count(program) | program
    ]
    |> :erlang.list_to_binary()
    # to hex and all lower case for better readability
    |> Base.encode16(case: :lower)
  end

  defp parse_segwit_data([]) do
    {:error, :empty_segwit_data}
  end

  defp parse_segwit_data([version | encoded]) when version in @valid_witness_version do
    case Bech32.convert_bits(encoded, 5, 8, false) do
      {:ok, program} ->
        if is_program_length_valid?(version, program) do
          {:ok, {version, program}}
        else
          {:error, :invalid_program_length}
        end

      {:error, error} ->
        {:error, error}
    end
  end

  defp parse_segwit_data(_), do: {:error, :invalid_witness_version}

  defp is_program_length_valid?(version, program)
       when length(program) in @valid_witness_program_length_range do
    case {version, length(program)} do
      # BIP141 specifies If the version byte is 0, but the witness program is neither 20 nor 32 bytes, the script must fail.
      {0, program_length} when program_length == 20 or program_length == 32 ->
        true

      {0, _} ->
        false

      _ ->
        true
    end
  end

  defp is_program_length_valid?(_, _), do: false

  defp parse_network('bc'), do: {:ok, :mainnet}
  defp parse_network('tb'), do: {:ok, :testnet}
  defp parse_network('bcrt'), do: {:ok, :regtest}
  defp parse_network(_), do: {:error, :invalid_network}

  defp witness_version_to_bech_encoding(0), do: :bech32
  defp witness_version_to_bech_encoding(witver) when witver in 1..16, do: :bech32m
end


text:
defmodule Bitcoinex.Utils do
  @moduledoc """
  Contains useful utility functions used in Bitcoinex.
  """

  @spec sha256(iodata()) :: binary
  def sha256(str) do
    :crypto.hash(:sha256, str)
  end

  def tagged_hash(tag, str) do
    tag_hash = sha256(tag)
    sha256(tag_hash <> tag_hash <> str)
  end

  @spec replicate(term(), integer()) :: list(term())
  def replicate(_num, 0) do
    []
  end

  def replicate(x, num) when x > 0 do
    for _ <- 1..num, do: x
  end

  @spec double_sha256(iodata()) :: binary
  def double_sha256(preimage) do
    :crypto.hash(
      :sha256,
      :crypto.hash(:sha256, preimage)
    )
  end

  @spec hash160(iodata()) :: binary
  def hash160(preimage) do
    :crypto.hash(
      :ripemd160,
      :crypto.hash(:sha256, preimage)
    )
  end

  @typedoc """
    The pad_type describes the padding to use.
  """
  @type pad_type :: :leading | :trailing

  @doc """
  pads binary according to the byte length and the padding type. A binary can be padded with leading or trailing zeros.
  """
  @spec pad(bin :: binary, byte_len :: integer, pad_type :: pad_type) :: binary
  def pad(bin, byte_len, _pad_type) when is_binary(bin) and byte_size(bin) == byte_len do
    bin
  end

  def pad(bin, byte_len, pad_type) when is_binary(bin) and pad_type == :leading do
    pad_len = 8 * byte_len - byte_size(bin) * 8
    <<0::size(pad_len)>> <> bin
  end

  def pad(bin, byte_len, pad_type) when is_binary(bin) and pad_type == :trailing do
    pad_len = 8 * byte_len - byte_size(bin) * 8
    bin <> <<0::size(pad_len)>>
  end

  @spec int_to_big(non_neg_integer(), non_neg_integer()) :: binary
  def int_to_big(i, p) do
    i
    |> :binary.encode_unsigned()
    |> pad(p, :leading)
  end

  def int_to_little(i, p) do
    i
    |> :binary.encode_unsigned(:little)
    |> pad(p, :trailing)
  end

  def little_to_int(i), do: :binary.decode_unsigned(i, :little)

  def encode_int(i) when i > 0 do
    cond do
      i < 0xFD -> :binary.encode_unsigned(i)
      i <= 0xFFFF -> <<0xFD>> <> int_to_little(i, 2)
      i <= 0xFFFFFFFF -> <<0xFE>> <> int_to_little(i, 4)
      i <= 0xFFFFFFFFFFFFFFFF -> <<0xFF>> <> int_to_little(i, 8)
      true -> {:error, "invalid integer size"}
    end
  end

  def hex_to_bin(str) do
    str
    |> String.downcase()
    |> Base.decode16(case: :lower)
    |> case do
      # In case of error, its already binary or its invalid
      :error -> {:error, "invalid string"}
      # valid binary
      {:ok, bin} -> bin
    end
  end

  # todo: better to just convert to ints and XOR them?
  @spec xor_bytes(binary, binary) :: binary
  def xor_bytes(bin0, bin1) do
    {bl0, bl1} = {:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)}

    Enum.zip(bl0, bl1)
    |> Enum.map(fn {b0, b1} -> Bitwise.bxor(b0, b1) end)
    |> :binary.list_to_bin()
  end
end


text:
defmodule Bitcoinex.Script do
  @moduledoc """
  	a module for manipulating Bitcoin Scripts
  """

  import Bitcoinex.Opcode

  alias Bitcoinex.Secp256k1.Point

  alias Bitcoinex.{Utils, Address, Segwit, Base58, Network}

  @wsh_length 32
  @tapkey_length 32
  @h160_length 20
  @pubkey_lengths [33, 65]

  @type script_type :: :p2pk | :p2pkh | :p2sh | :p2wpkh | :p2wsh | :p2tr | :multi | :non_standard

  @type t :: %__MODULE__{
          items: list
        }

  @enforce_keys [
    :items
  ]
  defstruct [:items]

  defguard is_valid_multi(m, pubkeys)
           when is_integer(m) and m > 0 and length(pubkeys) > 0 and length(pubkeys) >= m

  defp invalid_opcode_error(msg), do: {:error, "invalid opcode: #{msg}"}

  def is_valid_opcode(i) when is_integer(i), do: i >= 0x00 && i < 0xFF

  @doc """
  	new returns an empty script object.
  """
  @spec new() :: t()
  def new, do: %__MODULE__{items: []}

  @doc """
  	to_list returns the script as a list of items
  """
  @spec to_list(t()) :: list
  def to_list(%__MODULE__{items: script}), do: script

  @doc """
  	empty? returns true for empty scripts, false otherwise.
  """
  @spec empty?(t()) :: bool
  def empty?(%__MODULE__{items: []}), do: true
  def empty?(_), do: false

  @doc """
  	script_length returns the number of items in the script.
  """
  @spec script_length(t()) :: non_neg_integer()
  def script_length(%__MODULE__{items: items}), do: length(items)

  @doc """
  	byte_length returns the byte length of the serialized script.
  """
  @spec byte_length(t()) :: non_neg_integer()
  def byte_length(script) do
    script
    |> serialize_script()
    |> byte_size()
  end

  @doc """
    hash160 is a helper function which returns the hash160
    digest of the serialized script, as used in P2SH scripts.
  """
  @spec hash160(t()) :: binary
  def hash160(script = %__MODULE__{}) do
    script
    |> serialize_script()
    |> Utils.hash160()
  end

  @doc """
    hash256 is a helper function which returns the hash256
    digest of the serialized script, as used in P2WSH scripts.
  """
  @spec sha256(t()) :: binary
  def sha256(script = %__MODULE__{}) do
    script
    |> serialize_script()
    |> Utils.sha256()
  end

  @doc """
  	get_op_num returns the integer associated with the passed opcode atom.
  """
  @spec get_op_num(atom) :: {:ok, non_neg_integer()} | :error
  def get_op_num(op), do: Map.fetch(opcode_atoms(), op)

  @doc """
  	get_op_atom returns the atom associated with the passed opcode integer.
  """
  @spec get_op_atom(non_neg_integer()) :: non_neg_integer() | {:ok, atom}
  def get_op_atom(i), do: if(i > 0 and i < 0x4C, do: i, else: Map.fetch(opcode_nums(), i))

  @doc """
  	pop returns the first element of the script and the remaining script.
  	Returns nil if script is empty
  """
  @spec pop(t()) :: nil | {:ok, non_neg_integer() | binary, t()}
  def pop(%__MODULE__{items: []}), do: nil
  def pop(%__MODULE__{items: [item | stack]}), do: {:ok, item, %__MODULE__{items: stack}}

  @doc """
  	push_op pushes a single opcode to the script as an integer and returns the script.
  """
  @spec push_op(t(), atom | non_neg_integer()) :: {:ok, t()} | {:error, String.t()}
  def push_op(%__MODULE__{items: stack}, item) do
    # item is opcode num
    if is_integer(item) and item >= 0 and item < 0xFF do
      {:ok, %__MODULE__{items: [item | stack]}}
    else
      # item is atom
      case get_op_num(item) do
        :error -> invalid_opcode_error(item)
        {:ok, op} -> {:ok, %__MODULE__{items: [op | stack]}}
      end
    end
  end

  # used to push data lengths and raw binary
  defp push_raw_data(%__MODULE__{items: stack}, data) do
    %__MODULE__{items: [data | stack]}
  end

  @doc """
  	push_data returns a script with the binary data and any
  	accompanying pushdata or pushbytes opcodes added to the front of the script.
  """
  @spec push_data(t(), binary) :: {:ok, t()} | {:error, String.t()}
  def push_data(script = %__MODULE__{}, data) do
    datalen = byte_size(data)
    script = push_raw_data(script, data)

    cond do
      datalen < 0x4C ->
        push_op(script, datalen)

      datalen <= 0xFF ->
        push_op(script, :op_pushdata1)

      datalen <= 0xFFFF ->
        push_op(script, :op_pushdata2)

      datalen <= 0xFFFFFFFF ->
        push_op(script, :op_pushdata4)

      true ->
        {:error, "invalid data length, must be 0..0xffffffff, got #{datalen}"}
    end
  end

  # SERIALIZE & PARSE
  defp serializer(%__MODULE__{items: []}, acc), do: acc

  defp serializer(%__MODULE__{items: [item | script]}, acc) when is_integer(item) do
    # prevents UTF-8 ints from becoming strings
    serializer(%__MODULE__{items: script}, acc <> Utils.int_to_little(item, 1))
  end

  # For data pushes
  defp serializer(%__MODULE__{items: [item | script]}, acc) when is_binary(item) do
    len = byte_size(item)

    cond do
      # CHECK IF PUSHBYTES75 is valid
      len < 0x4C ->
        serializer(%__MODULE__{items: script}, acc <> item)

      len <= 0xFF ->
        len = len |> Utils.int_to_little(1)
        serializer(%__MODULE__{items: script}, acc <> len <> item)

      # PUSHDATA limited to 520 bytes, so no PUSHDATA2 > 520 is a valid script.
      # Should we allow this?
      len <= 0xFFFF ->
        len = Utils.int_to_little(len, 2)
        serializer(%__MODULE__{items: script}, acc <> len <> item)

      # no PUSHDATA4 is a valid script.
      # Should we allow this?
      len <= 0xFFFFFFFF ->
        len = Utils.int_to_little(len, 4)
        serializer(%__MODULE__{items: script}, acc <> len <> item)
    end
  end

  @doc """
  	serialize_script serializes the script into binary
  	according to Bitcoin's standard.
  """
  @spec serialize_script(t()) :: binary
  def serialize_script(script = %__MODULE__{}) do
    # serialize_script(%Script{items: [0x51]}) will still display "Q" but
    # it functions as binary 0x51. Use to_hex for displaying scripts.
    serializer(script, <<>>)
  end

  @doc """
  	to_hex returns the hex of a serialized script.
  """
  @spec to_hex(t()) :: String.t()
  def to_hex(script) do
    script
    |> serialize_script()
    |> Base.encode16(case: :lower)
  end

  @doc """
  	parse_script parses a binary or hex string into a script.
  """
  @spec parse_script(binary) :: {:ok, t()} | {:error, String.t()}
  def parse_script(script_str) when is_binary(script_str) do
    try do
      case Utils.hex_to_bin(script_str) do
        {:error, _msg} ->
          # necessary to allow parse_script to accept raw binary script
          parser(new(), script_str)

        bin ->
          parser(new(), bin)
      end
    rescue
      _ -> {:error, "invalid script. parse_script accepts hex or binary."}
    end
  end

  defp parser(script, <<>>), do: {:ok, script}

  defp parser(script, <<next::binary-size(1), bin::binary>>) do
    op = :binary.decode_unsigned(next)

    cond do
      # PUSHBYTES
      op > 0x00 and op < 0x4C ->
        {:ok, rest} = parser(script, :binary.part(bin, op, byte_size(bin) - op))

        rest
        |> push_raw_data(:binary.part(bin, 0, op))
        |> push_op(op)

      # PUSHDATA1
      op == 0x4C ->
        len = bin |> :binary.part(0, 1) |> Utils.little_to_int()
        {:ok, rest} = parser(script, :binary.part(bin, len + 1, byte_size(bin) - len - 1))

        rest
        |> push_raw_data(:binary.part(bin, 1, len))
        |> push_op(op)

      # PUSHDATA2
      op == 0x4D ->
        len = bin |> :binary.part(0, 2) |> Utils.little_to_int()
        {:ok, rest} = parser(script, :binary.part(bin, len + 2, byte_size(bin) - len - 2))

        rest
        |> push_raw_data(:binary.part(bin, 2, len))
        |> push_op(op)

      # PUSHDATA4
      op == 0x4E ->
        len = bin |> :binary.part(0, 4) |> Utils.little_to_int()
        {:ok, rest} = parser(script, :binary.part(bin, len + 4, byte_size(bin) - len - 4))

        rest
        |> push_raw_data(:binary.part(bin, 4, len))
        |> push_op(op)

      # OPCODE
      is_valid_opcode(op) ->
        {:ok, rest} = parser(script, :binary.part(bin, 0, byte_size(bin)))
        push_op(rest, op)

      true ->
        invalid_opcode_error(op)
    end
  end

  @doc """
  	raw_combine directly concatenates two scripts with no checks.
  """
  @spec raw_combine(t(), t()) :: t()
  def raw_combine(%__MODULE__{items: s1}, %__MODULE__{items: s2}),
    do: %__MODULE__{items: s1 ++ s2}

  @doc """
  	display_script returns a human readable string of the script, with
  	op_codes shown by name rather than number.
  """
  @spec display_script(t()) :: String.t()
  def display_script(script) do
    " " <> scriptxt = display_script(script, "")
    scriptxt
  end

  defp display_script(%__MODULE__{items: []}, acc), do: acc

  defp display_script(%__MODULE__{items: [item | stack]}, acc) when is_integer(item) do
    if item > 0 and item < 0x4C do
      display_script(%__MODULE__{items: stack}, acc <> " OP_PUSHBYTES_#{item}")
    else
      {:ok, op_atom} = get_op_atom(item)
      upper_op = op_atom |> to_string() |> String.upcase()
      display_script(%__MODULE__{items: stack}, acc <> " " <> upper_op)
    end
  end

  defp display_script(%__MODULE__{items: [item | stack]}, acc) when is_binary(item) do
    display_script(%__MODULE__{items: stack}, acc <> " " <> Base.encode16(item, case: :lower))
  end

  # SCRIPT TYPE DETERMINERS

  @doc """
  	is_p2pk? returns whether a given script is of the p2pk format:
  	<33-byte or 65-byte pubkey> OP_CHECKSIG
  """
  @spec is_p2pk?(t()) :: boolean
  def is_p2pk?(%__MODULE__{
        items: [len, pubkey, 0xAC]
      })
      when len in @pubkey_lengths and len == byte_size(pubkey) do
    true
  end

  def is_p2pk?(%__MODULE__{}), do: false

  @doc """
  	is_p2pkh? returns whether a given script is of the p2pkh format:
  	OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG
  """
  @spec is_p2pkh?(t()) :: boolean
  def is_p2pkh?(%__MODULE__{
        items: [0x76, 0xA9, @h160_length, <<_::binary-size(@h160_length)>>, 0x88, 0xAC]
      }),
      do: true

  def is_p2pkh?(%__MODULE__{}), do: false

  @doc """
  	is_p2sh? returns whether a given script is of the p2sh format:
  	OP_HASH160 OP_PUSHBYTES_20 <20-byte hash> OP_EQUAL
  """
  @spec is_p2sh?(t()) :: boolean
  def is_p2sh?(%__MODULE__{items: [0xA9, @h160_length, <<_::binary-size(@h160_length)>>, 0x87]}),
    do: true

  def is_p2sh?(%__MODULE__{}), do: false

  @doc """
  	is_p2wpkh? returns whether a given script is of the p2wpkh format:
  	OP_0 OP_PUSHBYTES_20 <20-byte hash>
  """
  @spec is_p2wpkh?(t()) :: boolean
  def is_p2wpkh?(%__MODULE__{items: [0x00, @h160_length, <<_::binary-size(@h160_length)>>]}),
    do: true

  def is_p2wpkh?(%__MODULE__{}), do: false

  @doc """
  	is_p2wsh? returns whether a given script is of the p2wsh format:
  	OP_0 OP_PUSHBYTES_32 <32-byte hash>
  """
  @spec is_p2wsh?(t()) :: boolean
  def is_p2wsh?(%__MODULE__{items: [0x00, @wsh_length, <<_::binary-size(@wsh_length)>>]}),
    do: true

  def is_p2wsh?(%__MODULE__{}), do: false

  @doc """
  	is_p2tr? returns whether a given script is of the p2tr format:
  	OP_1 OP_PUSHBYTES_32 <32-byte hash>
  """
  @spec is_p2tr?(t()) :: boolean
  def is_p2tr?(%__MODULE__{items: [0x51, @tapkey_length, <<_::binary-size(@tapkey_length)>>]}),
    do: true

  def is_p2tr?(%__MODULE__{}), do: false

  @doc """
  	is_multi? returns whether a given script is of the raw multisig format:
  	OP_(INT) [Public Keys] OP_(INT) OP_CHECKMULTISIG
  """
  @spec is_multi?(t()) :: boolean
  def is_multi?(%__MODULE__{items: [op_m | rest]})
      when op_m > 0x50 and op_m <= 0x60 and length(rest) > 3 do
    test_multi(rest, 0, op_m)
  end

  def is_multi?(_), do: false

  defp test_multi([op_n, 0xAE], n, m) when op_n == 0x50 + n and m <= op_n, do: true

  defp test_multi([op_push | [pk | rest]], n, m) when op_push in @pubkey_lengths do
    case Point.parse_public_key(pk) do
      {:ok, _pk} -> test_multi(rest, n + 1, m)
      {:error, _msg} -> false
    end
  end

  defp test_multi(_, _, _), do: false

  @doc """
    extract_multi_policy takes in a raw multisig script and returns the m, the
    number of signatures required and the n authorized public keys.
  """
  @spec extract_multi_policy(t()) ::
          {:ok, non_neg_integer(), list(Point.t())} | {:error, String.t()}
  def extract_multi_policy(script = %__MODULE__{items: [op_m | items]}) do
    if is_multi?(script) do
      {:ok, op_m - 0x50, extractor(items, [])}
    else
      {:error, "invalid raw multisig script"}
    end
  end

  defp extractor([_op_n, 0xAE], keys), do: keys

  defp extractor([_op_push | [key | items]], keys) do
    case Point.parse_public_key(key) do
      {:ok, pk} -> [pk | extractor(items, keys)]
      {:error, msg} -> {:error, "invalid public key: #{msg}"}
    end
  end

  @doc """
  	get_script_type determines the type of a script based on its elements
  	returns :non_standard if no type matches
  """
  @spec get_script_type(t()) :: script_type
  def get_script_type(script = %__MODULE__{}) do
    cond do
      # sorted by most prevalent
      is_p2pkh?(script) -> :p2pkh
      is_p2sh?(script) -> :p2sh
      is_p2wpkh?(script) -> :p2wpkh
      is_p2wsh?(script) -> :p2wsh
      is_p2pk?(script) -> :p2pk
      is_p2tr?(script) -> :p2tr
      is_multi?(script) -> :multi
      true -> :non_standard
    end
  end

  # CREATE COMMON SCRIPTS

  @doc """
  	create_p2pk creates a p2pk script using the passed public key
  """
  @spec create_p2pk(binary) :: {:ok, t()} | {:error, String.t()}
  def create_p2pk(pk) when is_binary(pk) and byte_size(pk) in [33, 65] do
    {:ok, s} = push_op(new(), 0xAC)
    push_data(s, pk)
  end

  def create_p2pk(_), do: {:error, "pubkey must be 33 or 65 bytes compressed or uncompressed SEC"}

  @doc """
  	create_p2pkh creates a p2pkh script using the passed 20-byte public key hash
  """
  @spec create_p2pkh(binary) :: {:ok, t()} | {:error, String.t()}
  def create_p2pkh(<<pkh::binary-size(@h160_length)>>) do
    {:ok, s} = push_op(new(), 0xAC)
    {:ok, s} = push_op(s, 0x88)
    {:ok, s} = push_data(s, pkh)
    {:ok, s} = push_op(s, 0xA9)
    push_op(s, 0x76)
  end

  def create_p2pkh(_), do: {:error, "pubkey hash must be a #{@h160_length}-byte hash"}

  @doc """
  	create_p2sh creates a p2sh script using the passed 20-byte public key hash
  """
  @spec create_p2sh(binary) :: {:ok, t()} | {:error, String.t()}
  def create_p2sh(<<sh::binary-size(@h160_length)>>) do
    {:ok, s} = push_op(new(), 0x87)
    {:ok, s} = push_data(s, sh)
    push_op(s, 0xA9)
  end

  def create_p2sh(_), do: {:error, "script hash must be a #{@h160_length}-byte hash"}

  @doc """
    to_p2sh wraps any script in a p2sh by first hashing it (hash160)
    and then wrapping then script hash in a p2sh script.
  """
  @spec to_p2sh(t()) :: {:ok, t()} | {:error, String.t()}
  def to_p2sh(script = %__MODULE__{}) do
    script
    |> hash160()
    |> create_p2sh()
  end

  @doc """
    create_multi creates a raw multisig script using m and the list of public keys.
  """
  @spec create_multi(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()}
  def create_multi(m, pubkeys) when is_valid_multi(m, pubkeys) do
    try do
      # checkmultisig
      {:ok, s} = push_op(new(), 0xAE)
      {:ok, s} = push_op(s, 0x50 + length(pubkeys))
      s = fill_multi_keys(s, pubkeys)
      push_op(s, 0x50 + m)
    rescue
      _ -> {:error, "invalid public key."}
    end
  end

  def create_multi(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"}

  defp fill_multi_keys(s, []), do: s

  defp fill_multi_keys(s, [pk = %Point{} | pubkeys]) do
    {:ok, s} = push_data(fill_multi_keys(s, pubkeys), Point.sec(pk))
    s
  end

  defp fill_multi_keys(_, _), do: raise(ArgumentError)

  @doc """
    create_p2sh_multi returns both a P2SH-wrapped multisig script
    and the underlying raw multisig script using m and the list of public keys.
  """
  @spec create_p2sh_multi(non_neg_integer(), list(Point.t())) ::
          {:ok, t(), t()} | {:error, String.t()}
  def create_p2sh_multi(m, pubkeys) do
    case create_multi(m, pubkeys) do
      {:ok, multi} ->
        h160 = hash160(multi)
        {:ok, p2sh} = create_p2sh(h160)
        {:ok, p2sh, multi}

      {:error, msg} ->
        {:error, msg}
    end
  end

  @doc """
    create_p2wsh_multi returns both a P2WSH-wrapped multisig script
    and the underlying raw multisig script using m and the list of public keys.
  """
  @spec create_p2wsh_multi(non_neg_integer(), list(Point.t())) ::
          {:ok, t(), t()} | {:error, String.t()}
  def create_p2wsh_multi(m, pubkeys) do
    case create_multi(m, pubkeys) do
      {:ok, multi} ->
        h256 = sha256(multi)
        {:ok, p2wsh} = create_p2wsh(h256)
        {:ok, p2wsh, multi}

      {:error, msg} ->
        {:error, msg}
    end
  end

  @doc """
  	create_witness_scriptpubkey creates any witness script from a witness version
  	and witness program. It performs no validity checks.
  """
  @spec create_witness_scriptpubkey(non_neg_integer(), binary) :: {:ok, t()}
  def create_witness_scriptpubkey(version, witness_program) do
    wit_version_adjusted = if(version == 0, do: 0, else: version + 0x50)
    {:ok, s} = push_data(new(), witness_program)
    push_op(s, wit_version_adjusted)
  end

  @doc """
  	create_p2wpkh creates a p2wpkh script using the passed 20-byte public key hash
  """
  @spec create_p2wpkh(binary) :: {:ok, t()}
  def create_p2wpkh(<<pkh::binary-size(@h160_length)>>),
    do: create_witness_scriptpubkey(0, pkh)

  def create_p2wpkh(_), do: {:error, "pubkey hash must be a #{@h160_length}-byte hash"}

  @doc """
  	create_p2wsh creates a p2wsh script using the passed 32-byte script hash
  """
  @spec create_p2wsh(binary) :: {:ok, t()}
  def create_p2wsh(<<sh::binary-size(@wsh_length)>>), do: create_witness_scriptpubkey(0, sh)
  def create_p2wsh(_), do: {:error, "script hash must be a #{@wsh_length}-byte hash"}

  @doc """
    to_p2wsh converts any script into a p2wsh script by hashing it (SHA256)
    then wrapping the script hash as a p2wsh script.
  """
  @spec to_p2wsh(t()) :: {:ok, t()}
  def to_p2wsh(script = %__MODULE__{}) do
    script
    |> sha256()
    |> create_p2wsh()
  end

  @doc """
  	create_p2tr creates a p2tr script using the passed 32-byte public key
    or Point. If a point is passed, it's interpreted as q, the full witness
    program or taproot output key per BIP 341 rather than the keyspend pubkey.
  """
  @spec create_p2tr(binary | Point.t()) :: {:ok, t()}
  def create_p2tr(<<pk::binary-size(@tapkey_length)>>), do: create_witness_scriptpubkey(1, pk)
  def create_p2tr(q = %Point{}), do: create_witness_scriptpubkey(1, Point.x_bytes(q))
  def create_p2tr(_), do: {:error, "public key must be #{@tapkey_length}-bytes"}

  @doc """
  	create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash
  """
  @spec create_p2sh_p2wpkh(binary) :: {:ok, t(), t()}
  def create_p2sh_p2wpkh(<<pkh::binary-size(@h160_length)>>) do
    {:ok, p2wpkh} = create_p2wpkh(pkh)

    {:ok, p2sh} =
      p2wpkh
      |> serialize_script()
      |> Utils.hash160()
      |> create_p2sh()

    # return both p2sh script and redeem script (p2wpkh script)
    {:ok, p2sh, p2wpkh}
  end

  def create_p2sh_p2wpkh(_), do: {:error, "public key hash must be #{@h160_length}-bytes"}

  # CREATE SCRIPTS FROM PUBKEYS

  @doc """
  	public_key_hash takes the hash160 of the public key's compressed sec encoding.
  	Can be used to create a pkh script.
  """
  @spec public_key_hash(Point.t()) :: binary
  def public_key_hash(p = %Point{}) do
    p
    |> Point.sec()
    |> Utils.hash160()
  end

  @doc """
  	public_key_to_p2pkh creates a p2pkh script from a public key.
  	All public keys are compressed.
  """
  @spec public_key_to_p2pkh(Point.t()) :: {:ok, t()}
  def public_key_to_p2pkh(p = %Point{}) do
    p
    |> public_key_hash()
    |> create_p2pkh()
  end

  def public_key_to_p2pkh(_), do: {:error, "invalid public key"}

  @doc """
  	public_key_to_p2wpkh creates a p2wpkh script from a public key.
  	All public keys are compressed.
  """
  @spec public_key_to_p2wpkh(Point.t()) :: {:ok, t()}
  def public_key_to_p2wpkh(p = %Point{}) do
    p
    |> public_key_hash()
    |> create_p2wpkh()
  end

  def public_key_to_p2wpkh(_), do: {:error, "invalid public key"}

  @doc """
  	public_key_to_p2sh_p2wpkh creates a p2sh-p2wpkh script from a public key.
  	All public keys are compressed.
  """
  @spec public_key_to_p2sh_p2wpkh(Point.t()) :: {:ok, t(), t()}
  def public_key_to_p2sh_p2wpkh(p = %Point{}) do
    p
    |> public_key_hash()
    |> create_p2sh_p2wpkh()
  end

  def public_key_to_p2sh_p2wpkh(_), do: {:error, "invalid public key"}

  # ADDRESS CREATION & DECODING

  @doc """
  	from_address produces the scriptpubkey from an address.
  """
  @spec from_address(String.t()) ::
          {:error, String.t()} | {:ok, t(), Bitcoinex.Network.network_name()}
  def from_address(addr) do
    case String.slice(addr, 0, 2) do
      # segwit addresses
      p when p in ["bc", "tb"] ->
        case Segwit.decode_address(addr) do
          {:ok, {network, version, program}} ->
            {:ok, script} = create_witness_scriptpubkey(version, :binary.list_to_bin(program))
            {:ok, script, network}

          {:error, msg} ->
            {:error, "invalid segwit address: #{msg}"}
        end

      # legacy addresses
      _ ->
        try do
          {:ok, <<pfx::little-size(8), body::binary>>} = Base58.decode(addr)
          tpkh = Network.testnet().p2pkh_version_decimal_prefix
          mpkh = Network.mainnet().p2pkh_version_decimal_prefix
          tsh = Network.testnet().p2sh_version_decimal_prefix
          msh = Network.mainnet().p2sh_version_decimal_prefix

          case pfx do
            # p2pkh testnet
            ^tpkh ->
              {:ok, s} = create_p2pkh(body)
              {:ok, s, :testnet}

            # p2pkh mainnet
            ^mpkh ->
              {:ok, s} = create_p2pkh(body)
              {:ok, s, :mainnet}

            # p2sh testnet
            ^tsh ->
              {:ok, s} = create_p2sh(body)
              {:ok, s, :testnet}

            # p2sh mainnet
            ^msh ->
              {:ok, s} = create_p2sh(body)
              {:ok, s, :mainnet}
          end
        rescue
          _ -> {:error, "invalid address"}
        end
    end
  end

  @doc """
  	to_address converts a script object into the proper address type
  """
  @spec to_address(t(), Network.network_name()) ::
          {:ok, String.t()} | {:error, String.t()}
  def to_address(script = %__MODULE__{}, network) do
    {:ok, head, script} = pop(script)

    case head do
      # segwit 0
      0x00 ->
        {:ok, len, script} = pop(script)
        {:ok, <<res::binary-size(len)>>, _script} = pop(script)

        if len in [@h160_length, @wsh_length] do
          Segwit.encode_address(network, 0, :binary.bin_to_list(res))
        else
          {:error, "invalid witness program length. Must be in [#{@h160_length}, #{@wsh_length}]"}
        end

      # segwit 1 (taproot)
      0x51 ->
        {:ok, @tapkey_length, script} = pop(script)
        {:ok, <<res::binary-size(@tapkey_length)>>, _script} = pop(script)
        Segwit.encode_address(network, 1, :binary.bin_to_list(res))

      # p2sh
      0xA9 ->
        {:ok, @h160_length, script} = pop(script)
        {:ok, <<res::binary-size(@h160_length)>>, _script} = pop(script)
        {:ok, Address.encode(res, network, :p2sh)}

      # p2pkh
      0x76 ->
        {:ok, 0xA9, script} = pop(script)
        {:ok, @h160_length, script} = pop(script)
        {:ok, <<res::binary-size(@h160_length)>>, _script} = pop(script)
        {:ok, Address.encode(res, network, :p2pkh)}

      _ ->
        {:error, "non standard script type"}
    end
  end
end

defimpl String.Chars, for: Bitcoinex.Script do
  def to_string(script) do
    Bitcoinex.Script.display_script(script)
  end
end


text:
defmodule Bitcoinex.Transaction do
  @moduledoc """
  Bitcoin on-chain transaction structure.
  Supports serialization of transactions.
  """
  alias Bitcoinex.Transaction
  alias Bitcoinex.Transaction.In
  alias Bitcoinex.Transaction.Out
  alias Bitcoinex.Transaction.Witness
  alias Bitcoinex.Utils
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  @type t() :: %__MODULE__{
          version: non_neg_integer(),
          inputs: list(In.t()),
          outputs: list(Out.t()),
          witnesses: list(Witness.t()),
          lock_time: non_neg_integer()
        }

  defstruct [
    :version,
    :inputs,
    :outputs,
    :witnesses,
    :lock_time
  ]

  @doc """
    Returns the TxID of the given tranasction.

    TxID is sha256(sha256(nVersion | txins | txouts | nLockTime))
  """
  def transaction_id(txn) do
    legacy_txn = TxUtils.serialize(%{txn | witnesses: []})

    Base.encode16(
      <<:binary.decode_unsigned(
          Utils.double_sha256(legacy_txn),
          :big
        )::little-size(256)>>,
      case: :lower
    )
  end

  @doc """
    Decodes a transaction in a hex encoded string into binary.
  """
  def decode(tx_hex) when is_binary(tx_hex) do
    case Base.decode16(tx_hex, case: :lower) do
      {:ok, tx_bytes} ->
        case parse(tx_bytes) do
          {:ok, txn} ->
            {:ok, txn}

          :error ->
            {:error, :parse_error}
        end

      :error ->
        {:error, :decode_error}
    end
  end

  # returns transaction
  defp parse(<<version::little-size(32), remaining::binary>>) do
    {is_segwit, remaining} =
      case remaining do
        <<1::size(16), segwit_remaining::binary>> ->
          {:segwit, segwit_remaining}

        _ ->
          {:not_segwit, remaining}
      end

    # Inputs.
    {in_counter, remaining} = TxUtils.get_counter(remaining)
    {inputs, remaining} = In.parse_inputs(in_counter, remaining)

    # Outputs.
    {out_counter, remaining} = TxUtils.get_counter(remaining)
    {outputs, remaining} = Out.parse_outputs(out_counter, remaining)

    # If flag 0001 is present, this indicates an attached segregated witness structure.
    {witnesses, remaining} =
      if is_segwit == :segwit do
        Witness.parse_witness(in_counter, remaining)
      else
        {nil, remaining}
      end

    <<lock_time::little-size(32), remaining::binary>> = remaining

    if byte_size(remaining) != 0 do
      :error
    else
      {:ok,
       %Transaction{
         version: version,
         inputs: inputs,
         outputs: outputs,
         witnesses: witnesses,
         lock_time: lock_time
       }}
    end
  end
end

defmodule Bitcoinex.Transaction.Utils do
  @moduledoc """
  Utilities for when dealing with transaction objects.
  """
  alias Bitcoinex.Transaction
  alias Bitcoinex.Transaction.In
  alias Bitcoinex.Transaction.Out
  alias Bitcoinex.Transaction.Witness

  @doc """
    Returns the Variable Length Integer used in serialization.

    Reference: https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
  """
  @spec get_counter(binary) :: {non_neg_integer(), binary()}
  def get_counter(<<counter::little-size(8), vec::binary>>) do
    case counter do
      # 0xFD followed by the length as uint16_t
      0xFD ->
        <<len::little-size(16), vec::binary>> = vec
        {len, vec}

      # 0xFE followed by the length as uint32_t
      0xFE ->
        <<len::little-size(32), vec::binary>> = vec
        {len, vec}

      # 0xFF followed by the length as uint64_t
      0xFF ->
        <<len::little-size(64), vec::binary>> = vec
        {len, vec}

      _ ->
        {counter, vec}
    end
  end

  @spec serialize(Transaction.t()) :: binary()
  def serialize(%Transaction{witnesses: witness} = txn)
      when is_list(witness) and length(witness) > 0 do
    version = <<txn.version::little-size(32)>>
    marker = <<0x00::big-size(8)>>
    flag = <<0x01::big-size(8)>>
    tx_in_count = serialize_compact_size_unsigned_int(length(txn.inputs))
    inputs = In.serialize_inputs(txn.inputs) |> :erlang.list_to_binary()
    tx_out_count = serialize_compact_size_unsigned_int(length(txn.outputs))
    outputs = Out.serialize_outputs(txn.outputs) |> :erlang.list_to_binary()
    witness = Witness.serialize_witness(txn.witnesses)
    lock_time = <<txn.lock_time::little-size(32)>>

    version <>
      marker <> flag <> tx_in_count <> inputs <> tx_out_count <> outputs <> witness <> lock_time
  end

  def serialize(txn) do
    version = <<txn.version::little-size(32)>>
    tx_in_count = serialize_compact_size_unsigned_int(length(txn.inputs))
    inputs = In.serialize_inputs(txn.inputs) |> :erlang.list_to_binary()
    tx_out_count = serialize_compact_size_unsigned_int(length(txn.outputs))
    outputs = Out.serialize_outputs(txn.outputs) |> :erlang.list_to_binary()
    lock_time = <<txn.lock_time::little-size(32)>>

    version <> tx_in_count <> inputs <> tx_out_count <> outputs <> lock_time
  end

  @doc """
    Returns the serialized variable length integer.
  """
  def serialize_compact_size_unsigned_int(compact_size) do
    cond do
      compact_size >= 0 and compact_size <= 0xFC ->
        <<compact_size::little-size(8)>>

      compact_size <= 0xFFFF ->
        <<0xFD>> <> <<compact_size::little-size(16)>>

      compact_size <= 0xFFFFFFFF ->
        <<0xFE>> <> <<compact_size::little-size(32)>>

      compact_size <= 0xFF ->
        <<0xFF>> <> <<compact_size::little-size(64)>>
    end
  end
end

defmodule Bitcoinex.Transaction.Witness do
  @moduledoc """
  Witness structure part of an on-chain transaction.
  """
  alias Bitcoinex.Transaction.Witness
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  @type t :: %__MODULE__{
          txinwitness: list(binary())
        }
  defstruct [
    :txinwitness
  ]

  @doc """
    Wtiness accepts a binary and deserializes it.
  """
  @spec witness(binary) :: t()
  def witness(witness_bytes) do
    {stack_size, witness_bytes} = TxUtils.get_counter(witness_bytes)

    {witness, _} =
      if stack_size == 0 do
        {%Witness{txinwitness: []}, witness_bytes}
      else
        {stack_items, witness_bytes} = parse_stack(witness_bytes, [], stack_size)
        {%Witness{txinwitness: stack_items}, witness_bytes}
      end

    witness
  end

  @spec serialize_witness(list(Witness.t())) :: binary
  def serialize_witness(witnesses) do
    serialize_witness(witnesses, <<>>)
  end

  defp serialize_witness([], serialized_witnesses), do: serialized_witnesses

  defp serialize_witness(witnesses, serialized_witnesses) do
    [witness | witnesses] = witnesses

    serialized_witness =
      if Enum.empty?(witness.txinwitness) do
        <<0x0::big-size(8)>>
      else
        stack_len = TxUtils.serialize_compact_size_unsigned_int(length(witness.txinwitness))

        field =
          Enum.reduce(witness.txinwitness, <<>>, fn v, acc ->
            {:ok, item} = Base.decode16(v, case: :lower)
            item_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(item))
            acc <> item_len <> item
          end)

        stack_len <> field
      end

    serialize_witness(witnesses, serialized_witnesses <> serialized_witness)
  end

  def parse_witness(0, remaining), do: {nil, remaining}

  def parse_witness(counter, witnesses) do
    parse(witnesses, [], counter)
  end

  defp parse(remaining, witnesses, 0), do: {Enum.reverse(witnesses), remaining}

  defp parse(remaining, witnesses, count) do
    {stack_size, remaining} = TxUtils.get_counter(remaining)

    {witness, remaining} =
      if stack_size == 0 do
        {%Witness{txinwitness: 0}, remaining}
      else
        {stack_items, remaining} = parse_stack(remaining, [], stack_size)
        {%Witness{txinwitness: stack_items}, remaining}
      end

    parse(remaining, [witness | witnesses], count - 1)
  end

  defp parse_stack(remaining, stack_items, 0), do: {Enum.reverse(stack_items), remaining}

  defp parse_stack(remaining, stack_items, stack_size) do
    {item_size, remaining} = TxUtils.get_counter(remaining)

    <<stack_item::binary-size(item_size), remaining::binary>> = remaining

    parse_stack(
      remaining,
      [Base.encode16(stack_item, case: :lower) | stack_items],
      stack_size - 1
    )
  end
end

defmodule Bitcoinex.Transaction.In do
  @moduledoc """
  Transaction Input part of an on-chain transaction.
  """
  alias Bitcoinex.Transaction.In
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  @type t :: %__MODULE__{
          prev_txid: binary(),
          prev_vout: non_neg_integer(),
          script_sig: binary(),
          sequence_no: non_neg_integer()
        }

  defstruct [
    :prev_txid,
    :prev_vout,
    :script_sig,
    :sequence_no
  ]

  @spec serialize_inputs(list(In.t())) :: iolist()
  def serialize_inputs(inputs) do
    serialize_input(inputs, [])
  end

  defp serialize_input([], serialized_inputs), do: serialized_inputs

  defp serialize_input(inputs, serialized_inputs) do
    [input | inputs] = inputs

    {:ok, prev_txid} = Base.decode16(input.prev_txid, case: :lower)

    prev_txid =
      prev_txid
      |> :binary.decode_unsigned(:big)
      |> :binary.encode_unsigned(:little)
      |> Bitcoinex.Utils.pad(32, :trailing)

    {:ok, script_sig} = Base.decode16(input.script_sig, case: :lower)

    script_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(script_sig))

    serialized_input = [
      prev_txid,
      <<input.prev_vout::little-size(32)>>,
      script_len,
      script_sig,
      <<input.sequence_no::little-size(32)>>
    ]

    serialize_input(inputs, [serialized_inputs, serialized_input])
  end

  def parse_inputs(counter, inputs) do
    parse(inputs, [], counter)
  end

  defp parse(remaining, inputs, 0), do: {Enum.reverse(inputs), remaining}

  defp parse(
         <<prev_txid::binary-size(32), prev_vout::little-size(32), remaining::binary>>,
         inputs,
         count
       ) do
    {script_len, remaining} = TxUtils.get_counter(remaining)

    <<script_sig::binary-size(script_len), sequence_no::little-size(32), remaining::binary>> =
      remaining

    input = %In{
      prev_txid:
        Base.encode16(<<:binary.decode_unsigned(prev_txid, :big)::little-size(256)>>, case: :lower),
      prev_vout: prev_vout,
      script_sig: Base.encode16(script_sig, case: :lower),
      sequence_no: sequence_no
    }

    parse(remaining, [input | inputs], count - 1)
  end
end

defmodule Bitcoinex.Transaction.Out do
  @moduledoc """
  Transaction Output part of an on-chain transaction.
  """
  alias Bitcoinex.Transaction.Out
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  @type t :: %__MODULE__{
          value: non_neg_integer(),
          script_pub_key: binary()
        }

  defstruct [
    :value,
    :script_pub_key
  ]

  @spec serialize_outputs(list(Out.t())) :: iolist()
  def serialize_outputs(outputs) do
    serialize_output(outputs, [])
  end

  defp serialize_output([], serialized_outputs), do: serialized_outputs

  defp serialize_output(outputs, serialized_outputs) do
    [output | outputs] = outputs

    {:ok, script_pub_key} = Base.decode16(output.script_pub_key, case: :lower)

    script_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(script_pub_key))

    serialized_output = [<<output.value::little-size(64)>>, script_len, script_pub_key]
    serialize_output(outputs, [serialized_outputs, serialized_output])
  end

  def output(out_bytes) do
    <<value::little-size(64), out_bytes::binary>> = out_bytes
    {script_len, out_bytes} = TxUtils.get_counter(out_bytes)
    <<script_pub_key::binary-size(script_len), _::binary>> = out_bytes
    %Out{value: value, script_pub_key: Base.encode16(script_pub_key, case: :lower)}
  end

  def parse_outputs(counter, outputs) do
    parse(outputs, [], counter)
  end

  defp parse(remaining, outputs, 0), do: {Enum.reverse(outputs), remaining}

  defp parse(<<value::little-size(64), remaining::binary>>, outputs, count) do
    {script_len, remaining} = TxUtils.get_counter(remaining)

    <<script_pub_key::binary-size(script_len), remaining::binary>> = remaining

    output = %Out{
      value: value,
      script_pub_key: Base.encode16(script_pub_key, case: :lower)
    }

    parse(remaining, [output | outputs], count - 1)
  end
end