diff --git a/README.md b/README.md index 1395002c..ba9f3160 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - **🌐 Multi-node Support** - Enable multiple nodes specifically for rollback testing scenarios - **📊 Built-in Indexer** - Integrated Yaci Store with Blockfrost-compatible APIs - **🎯 Developer Tools** - Browser-based viewer, CLI management, and extensive APIs -- **🔗 SDK Integration** - Seamless integration with popular Cardano SDKs (Mesh, CCL, Lucid Evolution) +- **🔗 SDK Integration** - Seamless integration with popular Cardano SDKs (Mesh, CCL, Lucid Evolution, Sutra) ## 🏗️ Architecture diff --git a/docs/pages/tutorials/sutra-elixir/overview.mdx b/docs/pages/tutorials/sutra-elixir/overview.mdx new file mode 100644 index 00000000..e3e59096 --- /dev/null +++ b/docs/pages/tutorials/sutra-elixir/overview.mdx @@ -0,0 +1,84 @@ +## Configuration + +```elixir +config :sutra, :yaci, + yaci_general_api_url: "http://localhost:8080", # Defaults to http://localhost:8080 + yaci_admin_api_url: "http://localhost:10000" # Defaults to http://localhost:10000 +``` + +## Privnet Testing + +Sutra SDK includes a dedicated testing module `Sutra.PrivnetTest` to simplify integration testing against a private network (like Yaci DevKit). + +## Setup + +Ensure you have a local Yaci DevKit instance running. + +In your test file, `use Sutra.PrivnetTest`: + +```elixir +defmodule MyProject.MyTest do + use Sutra.PrivnetTest + + test "test something" do + # ... + end +end +``` + +This automatically checks if Yaci is running and sets up the provider configuration. + +## Managing Wallets + +### Default Wallets + +Access pre-funded default wallets from the Yaci DevKit (indices 0-20): + +```elixir +test "uses default wallet" do + with_default_wallet(0, fn %{address: address, signing_key: key} -> + # Test logic here + IO.puts("Wallet Address: #{address}") + end) +end +``` + +The wallet is automatically topped up if its balance is zero. + +### New Ephemeral Wallets + +Create a fresh wallet for isolation: + +```elixir +test "uses fresh wallet" do + with_new_wallet(fn %{address: address, signing_key: key} -> + # This wallet is funded with 100 ADA by default + end) +end +``` + +## Loading Funds + +You can explicitly load funds into an address: + +```elixir +# Load 1000 ADA +load_ada(address, 1000) + +# Load multiple UTxOs +load_ada(address, [1000, 500]) +``` + +## Waiting for Transactions + +Since blockchain operations are asynchronous, use `await_tx` to wait for a transaction to be confirmed: + +```elixir +tx_id = submit_some_transaction() + +# detailed info +tx_info = await_tx(tx_id) + +# or assert success +assert Sutra.PrivnetTest.await_tx(tx_id) +``` \ No newline at end of file diff --git a/e2e-tests/sutra-elixir/README.md b/e2e-tests/sutra-elixir/README.md new file mode 100644 index 00000000..760f1ac6 --- /dev/null +++ b/e2e-tests/sutra-elixir/README.md @@ -0,0 +1,59 @@ +# Sutra Elixir Scripts + +This README explains how to install Elixir and run the example Elixir scripts in this folder. + +## 1. Install Elixir + +On Debian/Ubuntu you can install Elixir with: + +```bash +sudo apt-get update +sudo apt-get install -y elixir +``` + +For other platforms or the latest release, see the official instructions: https://elixir-lang.org/install.html + +## 2. Run Tests + +- Native Script Minting: + +```bash +elixir nativescript_mint.exs +``` + +- Plutus Script Minting: + +```bash +elixir plutus_mint.exs +``` + +- Register stake credential: + +```bash +elixir register_stake.exs +``` + +- Delegate vote to DRep: + +```bash +elixir delegate_vote.exs +``` + +- Withdraw stake: + +```bash +elixir withdraw_stake.exs +``` + +> **Warning:** +> +> You must run `register_stake.exs` at least once before delegating to a DRep or withdrawing stake. + +> **Note:** +> +> If your Yaci Devkit is running on a custom endpoint, export these environment variables before running the scripts: +> +> ```bash +> export YACI_GENERAL_API_URL="http://your-host:8080" +> export YACI_ADMIN_API_URL="http://your-host:10000" +> ``` diff --git a/e2e-tests/sutra-elixir/delegate_vote.exs b/e2e-tests/sutra-elixir/delegate_vote.exs new file mode 100644 index 00000000..d6faa7f6 --- /dev/null +++ b/e2e-tests/sutra-elixir/delegate_vote.exs @@ -0,0 +1,53 @@ +# +# Note: This test assumes Native script and Plutus script staking credentials are already registered on chain. +# If not, please run elixir register_stake.exs before running this test. + +alias Sutra.Cardano.Common.Drep +alias Sutra.Cardano.Script.NativeScript +alias Sutra.Data +alias Sutra.Cardano.Script +alias Sutra.Crypto.Key + +Code.eval_file("setup.exs") + +mnemonic = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + +{:ok, root_key} = Key.root_key_from_mnemonic(mnemonic) +{:ok, extended_key} = Key.derive_child(root_key, 0, 0) + +{:ok, wallet_address} = Key.address(extended_key, :preprod) + +{:ok, extended_key_2} = Key.derive_child(root_key, 0, 0) + +{:ok, wallet_address_2} = Key.address(extended_key_2, :preprod) + + +always_true_script = "58da010100229800aba2aba1aab9eaab9dab9a9bae002488888966002646465300130063754003300800398040012444b30013370e9000001c4c9289bae300b300a375400915980099b874800800e2646644944c030004c030c034004c028dd5002456600266e1d2004003899251300b300a375400915980099b874801800e2646644944dd698060009806180680098051baa0048acc004cdc3a40100071324a2601660146ea80122646644944dd698060009806180680098051baa00440208041008201040203007300800130070013004375400f149a26cac80101" + +script = + always_true_script + |> Script.apply_params([Base.encode16("sutra-yaci-devkit-register-stake", case: :lower)]) + |> Script.new(:plutus_v3) + +script_json = %{ + "type" => "all", + "scripts" => [ + %{ + "type" => "sig", + "keyHash" => wallet_address.payment_credential.hash + } + ] +} + +tx_id = + Sutra.new_tx() + |> Sutra.delegate_vote(script, Drep.abstain(), Data.void()) + |> Sutra.delegate_vote(NativeScript.from_json(script_json), Drep.no_confidence()) + |> Sutra.delegate_vote(wallet_address, Drep.abstain()) + |> Sutra.build_tx!(wallet_address: [wallet_address, wallet_address_2]) + |> Sutra.sign_tx([extended_key, extended_key_2]) + |> Sutra.sign_tx_with_raw_extended_key(extended_key.stake_key) + |> Sutra.submit_tx() + +IO.inspect(tx_id) diff --git a/e2e-tests/sutra-elixir/nativescript_mint.exs b/e2e-tests/sutra-elixir/nativescript_mint.exs new file mode 100644 index 00000000..721b8160 --- /dev/null +++ b/e2e-tests/sutra-elixir/nativescript_mint.exs @@ -0,0 +1,52 @@ +alias Sutra.Cardano.Address +alias Sutra.Cardano.Asset +alias Sutra.Crypto.Key +alias Sutra.Cardano.Script +alias Sutra.Cardano.Script.NativeScript + + +# Setup sutra package & init yaci provider +Code.eval_file("setup.exs") + +mnemonic = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + +{:ok, root_key} = Key.root_key_from_mnemonic(mnemonic) +{:ok, extended_key} = Key.derive_child(root_key, 0, 0) + +{:ok, wallet_address} = Key.address(extended_key, :preprod) + +script_json = %{ + "type" => "all", + "scripts" => [ + %{ + "type" => "sig", + "keyHash" => wallet_address.payment_credential.hash + } + ] +} + +script = NativeScript.from_json(script_json) + +policy_id = NativeScript.to_script(script) |> Script.hash_script() + +assets = %{ + Base.encode16("SUTRA-NATIVE-TKN") => 1 +} + +tx_id = + Sutra.new_tx() + |> Sutra.mint_asset(policy_id, assets, script) + |> Sutra.add_output(wallet_address, %{ + policy_id => assets + }) + |> Sutra.add_output( + Address.from_script(policy_id, :testnet), + Asset.from_lovelace(1000), + {:datum_hash, "check As Hash"} + ) + |> Sutra.build_tx!(wallet_address: [wallet_address]) + |> Sutra.sign_tx([extended_key]) + |> Sutra.submit_tx() + +IO.puts("Tx Submitted for Mint with Native Script TxId: #{tx_id}") diff --git a/e2e-tests/sutra-elixir/plutus_mint.exs b/e2e-tests/sutra-elixir/plutus_mint.exs new file mode 100644 index 00000000..28d7665f --- /dev/null +++ b/e2e-tests/sutra-elixir/plutus_mint.exs @@ -0,0 +1,56 @@ +defmodule AlwaysSucceedMint do + @moduledoc false + + alias Sutra.Cardano.Address + alias Sutra.Cardano.Asset + alias Sutra.Cardano.Script + alias Sutra.Crypto.Key + alias Sutra.Data + + @compiled_code "589a010100323232323223225333004323232323232533300a3370e9000000899251375c601a60186ea800854ccc028cdc3a4004002264664464a66601c66e1d2000300f37540042a66601c66e1cdd6980898081baa00200114a22c2c6eb4018c038004c038c03c004c030dd50010b18051baa001300b300c003300a002300900230090013006375400229309b2b1bae0015734aae7555cf2ba15745" + + def run() do + + mnemonic = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + + {:ok, root_key} = Key.root_key_from_mnemonic(mnemonic) + + # deriving two account to have utxo to cover collateral + {:ok, extended_key_acct1} = Key.derive_child(root_key, 0, 0) + {:ok, wallet_address_1} = Key.address(extended_key_acct1, :preprod) + + + {:ok, extended_key_acct2} = Key.derive_child(root_key, 1, 0) + {:ok, wallet_address_2} = Key.address(extended_key_acct2, :preprod) + + simple_mint_script = + @compiled_code + |> Script.apply_params([Base.encode16("some-params", case: :lower)]) + |> Script.new(:plutus_v3) + + mint_script_address = Address.from_script(simple_mint_script, :preprod) + + out_token_name = Base.encode16("YACI-DEVKIT-TEST", case: :lower) + policy_id = Script.hash_script(simple_mint_script) + + out_value = + Asset.zero() + |> Asset.add(policy_id, out_token_name, 100) + + tx_id = + Sutra.new_tx() + |> Sutra.mint_asset(policy_id, %{out_token_name => 100}, simple_mint_script, Data.void()) + |> Sutra.add_output(mint_script_address, out_value, {:inline_datum, 58}) + |> Sutra.build_tx!(wallet_address: [wallet_address_1, wallet_address_2]) + |> Sutra.sign_tx([extended_key_acct1, extended_key_acct2]) + |> Sutra.submit_tx() + + IO.puts(" Tx submitted with : #{tx_id}") + end +end + +# Setup sutra package & init yaci provider +Code.eval_file("setup.exs") + +AlwaysSucceedMint.run() diff --git a/e2e-tests/sutra-elixir/register_stake.exs b/e2e-tests/sutra-elixir/register_stake.exs new file mode 100644 index 00000000..8d5a1691 --- /dev/null +++ b/e2e-tests/sutra-elixir/register_stake.exs @@ -0,0 +1,49 @@ +alias Sutra.Cardano.Script.NativeScript +alias Sutra.Data +alias Sutra.Cardano.Script +alias Sutra.Crypto.Key + +Code.eval_file("setup.exs") + +mnemonic = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + +{:ok, root_key} = Key.root_key_from_mnemonic(mnemonic) +{:ok, extended_key} = Key.derive_child(root_key, 0, 0) + +{:ok, wallet_address} = Key.address(extended_key, :preprod) + + +{:ok, extended_key_2} = Key.derive_child(root_key, 0, 0) + +{:ok, wallet_address_2} = Key.address(extended_key, :preprod) + +always_true_script = "58da010100229800aba2aba1aab9eaab9dab9a9bae002488888966002646465300130063754003300800398040012444b30013370e9000001c4c9289bae300b300a375400915980099b874800800e2646644944c030004c030c034004c028dd5002456600266e1d2004003899251300b300a375400915980099b874801800e2646644944dd698060009806180680098051baa0048acc004cdc3a40100071324a2601660146ea80122646644944dd698060009806180680098051baa00440208041008201040203007300800130070013004375400f149a26cac80101" + + +script = + always_true_script + |> Script.apply_params([Base.encode16("sutra-yaci-devkit-register-stake", case: :lower)]) + |> Script.new(:plutus_v3) + +script_json = %{ + "type" => "all", + "scripts" => [ + %{ + "type" => "sig", + "keyHash" => wallet_address.payment_credential.hash + } + ] +} + +tx_id = + Sutra.new_tx() + |> Sutra.register_stake_credential(script, Data.void()) + |> Sutra.register_stake_credential(NativeScript.from_json(script_json)) + |> Sutra.register_stake_credential(wallet_address) + |> Sutra.build_tx!(wallet_address: [wallet_address, wallet_address_2]) + |> Sutra.sign_tx([extended_key, extended_key_2]) + |> Sutra.sign_tx_with_raw_extended_key(extended_key.stake_key) + |> Sutra.submit_tx() + +IO.inspect(tx_id) diff --git a/e2e-tests/sutra-elixir/setup.exs b/e2e-tests/sutra-elixir/setup.exs new file mode 100644 index 00000000..81525916 --- /dev/null +++ b/e2e-tests/sutra-elixir/setup.exs @@ -0,0 +1,18 @@ +Mix.install([:sutra_cardano]) + +alias Sutra.Provider.Yaci + +# Setup Yaci Devkit provider +Application.put_env(:sutra, :provider, Yaci) + +Application.put_env( + :sutra, + :yaci_general_api_url, + System.get_env("YACI_GENERAL_API_URL", "http://localhost:8080") +) + +Application.put_env( + :sutra, + :yaci_admin_api_url, + System.get_env("YACI_ADMIN_API_URL", "http://localhost:10000") +) diff --git a/e2e-tests/sutra-elixir/withdraw_stake.exs b/e2e-tests/sutra-elixir/withdraw_stake.exs new file mode 100644 index 00000000..8136f403 --- /dev/null +++ b/e2e-tests/sutra-elixir/withdraw_stake.exs @@ -0,0 +1,46 @@ +alias Sutra.Cardano.Script.NativeScript +alias Sutra.Data +alias Sutra.Cardano.Script +alias Sutra.Crypto.Key + +Code.eval_file("setup.exs") + +mnemonic = + "test test test test test test test test test test test test test test test test test test test test test test test sauce" + +{:ok, root_key} = Key.root_key_from_mnemonic(mnemonic) + +{:ok, extended_key} = Key.derive_child(root_key, 0, 0) +{:ok, wallet_address} = Key.address(extended_key, :preprod) + +{:ok, extended_key_2} = Key.derive_child(root_key, 0, 0) +{:ok, wallet_address_2} = Key.address(extended_key, :preprod) + +always_true_script = "58da010100229800aba2aba1aab9eaab9dab9a9bae002488888966002646465300130063754003300800398040012444b30013370e9000001c4c9289bae300b300a375400915980099b874800800e2646644944c030004c030c034004c028dd5002456600266e1d2004003899251300b300a375400915980099b874801800e2646644944dd698060009806180680098051baa0048acc004cdc3a40100071324a2601660146ea80122646644944dd698060009806180680098051baa00440208041008201040203007300800130070013004375400f149a26cac80101" + +script = + always_true_script + |> Script.apply_params([Base.encode16("sutra-yaci-devkit-register-stake", case: :lower)]) + |> Script.new(:plutus_v3) + +script_json = %{ + "type" => "all", + "scripts" => [ + %{ + "type" => "sig", + "keyHash" => wallet_address.payment_credential.hash + } + ] +} + +tx_id = + Sutra.new_tx() + |> Sutra.withdraw_stake(script, Data.void(), 0) + |> Sutra.withdraw_stake(NativeScript.from_json(script_json), 0) + |> Sutra.withdraw_stake(wallet_address, 0) + |> Sutra.build_tx!(wallet_address: [wallet_address, wallet_address_2]) + |> Sutra.sign_tx([extended_key, extended_key_2]) + |> Sutra.sign_tx_with_raw_extended_key(extended_key.stake_key) + |> Sutra.submit_tx() + +IO.inspect(tx_id)