diff --git a/Cargo.lock b/Cargo.lock index 140daec..38da55c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,7 +607,7 @@ checksum = "382ce8820a5bb815055d3553a610e8cb542b2d767bbacea99038afda96cd760d" [[package]] name = "cshell" -version = "0.11.1" +version = "0.12.0" dependencies = [ "anyhow", "backoff", @@ -3589,9 +3589,9 @@ dependencies = [ [[package]] name = "tx3-cardano" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5f14e3e1b68b1bff81d58c891604a3087f34be70c2a2ad58a8605b6f15ed82" +checksum = "9532e9d3e4f40b02b88023c082bd1e27c3e3467bb6a749f7b261e82712b0941c" dependencies = [ "hex", "pallas", @@ -3604,9 +3604,9 @@ dependencies = [ [[package]] name = "tx3-lang" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7607218726810f53079b50a63ff4b5e25a8ccf53a82a80d7b6cc1815feec7a65" +checksum = "f9ffdc30232bfe0bfeced3d48768b8f00ed9f3015d9e78323ecda4cf45cbbd56" dependencies = [ "bincode", "hex", diff --git a/Cargo.toml b/Cargo.toml index 7611b22..df81caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ repository = "https://github.com/txpipe/cshell" license = "Apache-2.0" homepage = "https://docs.txpipe.io/cshell" -keywords = ["cardano", "blockchain", "wallet", "cardano"] +keywords = ["cardano", "blockchain", "wallet"] categories = ["command-line-utilities", "blockchain", "cardano", "wallet"] [dependencies] -tx3-lang = "0.11.1" -tx3-cardano = "0.11.1" +tx3-lang = "0.11.5" +tx3-cardano = "0.11.5" tx3-sdk = "^0" # tx3-lang = { git = "https://github.com/tx3-lang/tx3.git" } diff --git a/docs/commands/_meta.yml b/docs/commands/_meta.yml new file mode 100644 index 0000000..cf38a40 --- /dev/null +++ b/docs/commands/_meta.yml @@ -0,0 +1,3 @@ +label: Commands +order: 5 +collapsed: true \ No newline at end of file diff --git a/docs/commands/search.mdx b/docs/commands/search.mdx new file mode 100644 index 0000000..641724c --- /dev/null +++ b/docs/commands/search.mdx @@ -0,0 +1,305 @@ +--- +title: Search Commands +sidebar: + order: 6 + label: search +--- + +import { Aside, CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; + +The `search` command allows you to query blockchain data directly from the command line. You can search for blocks and transactions using various parameters. + + + +## Available Commands + +- **`search block`**: Query block information by tx-hash,slot +- **`search transaction`**: Query transaction details by hash + +## Output Formats + +All search commands support multiple output formats: + +```bash +# Table format (default) +cshell search block + +# JSON format +cshell search block --output-format json +``` + +### Supported Formats + +- **table**: Human-readable table format (default) +- **json**: Machine-readable JSON format + + + +--- + +## search block + +The `search block` command queries blockchain data to retrieve information about a specific block. + +### Usage + +```bash +cshell search block +``` + +### Options + +Run `cshell search block --help` to see all available options. + +#### Output Format + +Use the `--output-format` flag to change the output format: + +```bash +cshell search block --output-format json +``` + +Supported formats: +- `table` (default): Human-readable table +- `json`: Machine-readable JSON + +### Block Identifiers + +You can search for blocks using: + +```bash +cshell search block 9a28855928d8a94ac0ec7a5c0a45298cdbf939d1f302deb2b9e54bafb48789f4,91460405 +``` + +### Examples + +#### Basic Block Query + + + +```bash +cshell search block +``` + +Output: +``` +┌──────────────┬────┬───────────────────────────────────┬────────┬─────────┬──────────────┬────────────┬────────┐ +│ Block │ │ Hash │ Inputs │ Outputs │ Certificates │ Ref Inputs │ Datum │ +├──────────────┼────┼───────────────────────────────────┼────────┼─────────┼──────────────┼────────────┼────────┤ +│ 8f3a...1f2 │ 0 │ 7b2c3d4e5f6a7b8c9d0e1f2a3b4c5... │ 2 │ 3 │ 0 │ 0 │ empty │ +│ 8f3a...1f2 │ 1 │ 9c1d2e3f4a5b6c7d8e9f0a1b2c3d4... │ 1 │ 2 │ 0 │ 0 │ empty │ +└──────────────┴────┴───────────────────────────────────┴────────┴─────────┴──────────────┴────────────┴────────┘ +``` + + +```bash +cshell search block --output-format json +``` + +Output: +```json +{ + "auxiliary": {}, + "collateral": {}, + "fee": "200000", + "hash": "....", + "inputs": [ + ... + ], + "outputs": [ + ... + ], + "successful": true, + "validity": {}, + "witnesses": { + ... + } +} +``` + + + +#### Export Block Data + +Save block data to a file for later analysis: + +```bash +cshell search block --output-format json > block-data.json +``` + +--- + +## search transaction + +The `search transaction` command queries blockchain data to retrieve detailed information about a specific transaction. + +### Usage + +```bash +cshell search transaction +``` + +### Options + +Run `cshell search transaction --help` to see all available options. + +#### Output Format + +Use the `--output-format` flag to change the output format: + +```bash +cshell search transaction --output-format json +``` + +Supported formats: +- `table` (default): Human-readable table +- `json`: Machine-readable JSON + +### Examples + +#### Basic Transaction Query + + + +```bash +cshell search transaction ef6350af39d35caa3130218f8fb103fa6bb585258d52366966ede8da2100d3c1 +``` + +Output: +``` +┌──────────────┬────┬───────────────────────────────────┬────────┬─────────┬──────────────┬────────────┬─────────┐ +│ Block │ │ Hash │ Inputs │ Outputs │ Certificates │ Ref Inputs │ Datum │ +├──────────────┼────┼───────────────────────────────────┼────────┼─────────┼──────────────┼────────────┼─────────┤ +│ f55c...19db │ 0 │ ef6350af39d35caa3130218f8fb10... │ 1 │ 2 │ 0 │ 1 │ contain │ +└──────────────┴────┴───────────────────────────────────┴────────┴─────────┴──────────────┴────────────┴─────────┘ +``` + + +```bash +cshell search transaction --output-format json +``` + +Output: +```json +{ + "auxiliary": {}, + "collateral": {}, + "fee": "174389", + "hash": "72NQrznTXKoxMCGPj7ED+mu1hSWNUjZpZu3o2iEA08E=", + "inputs": [ + { + "outputIndex": 1, + "txHash": "2IQhCcVhqReK7BKTf604tlW/mG1LyDA3tKfX4eE9XBY=" + } + ], + "outputs": [ + { + "address": "cIe32DcyeHxWkqWXPdWwJEdAYiQQa6InY+6DaNY=", + "coin": "30000000", + "datum": { + ... + } + }, + { + "address": "AFmvO2CJMAJJi9D8hL1k+Eh+7LDfhFrZ5gkyqoZ06yLq0b9uxm0QfpFnclhfG8g5C3ZJj74KBTZe", + "coin": "9729302444", + "datum": {} + } + ], + "referenceInputs": [ + ... + ], + "successful": true, + "validity": {}, + "witnesses": { + ... + } +} +``` + + + +#### Export Transaction Data + +Save transaction details to a file: + +```bash +cshell search transaction --output-format json > transaction.json +``` + +#### Check Transaction Outputs + +Extract specific outputs from a transaction: + +```bash +cshell search transaction --output-format json | jq '.outputs' +``` + +### Use Cases + +#### 1. Transaction Confirmation + +Verify your submitted transaction: + +```bash +cshell search transaction +``` + +If found, the transaction was successfully submitted and confirmed. + +#### 2. UTxO Verification + +Check if a specific UTxO exists and is unspent: + +```bash +cshell search transaction --output-format json | jq '.outputs[0]' +``` + +#### 3. Payment Verification + +Confirm payment was received: + +```bash +# Search for transaction +cshell search transaction --output-format json + +# Check outputs for your address +jq '.outputs[] | select(.address == "addr1...")' transaction.json +``` + +#### 4. Smart Contract Debugging + +Inspect transaction datum and redeemers: + +```bash +cshell search transaction --output-format json | jq '.outputs[].datum' +``` + +#### 5. Token Verification + +Check native token transfers: + +```bash +cshell search transaction --output-format json | jq '.outputs[].assets' +``` + +## Quick Reference + + + +## Related Commands + +- [`tx invoke`](/cshell/commands/tx) - Submit transactions +- [`wallet balance`](/cshell/wallet) - Check wallet balance +- [`explorer`](/cshell/explorer) - Visual block and transaction explorer diff --git a/docs/commands/tx.mdx b/docs/commands/tx.mdx new file mode 100644 index 0000000..3a0af8a --- /dev/null +++ b/docs/commands/tx.mdx @@ -0,0 +1,374 @@ +--- +title: Transaction Commands +sidebar: + order: 0 + label: tx +--- + +import { Aside, CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; + +The `tx` command (or `transactions`) provides a complete workflow for managing Cardano transactions in Cshell. You can execute transactions using [**tx3**](https://docs.txpipe.io/tx3) files, or work with individual steps of the transaction lifecycle. + + + +## Transaction Workflow + +Understanding the transaction lifecycle: + +1. **Write**: Create a tx3 file describing your transaction +2. **Resolve**: Convert tx3 to CBOR format (handled by TRP) +3. **Sign**: Add cryptographic signatures using your wallet +4. **Submit**: Send the transaction to the blockchain + +The `tx invoke` command handles all these steps automatically, while individual commands let you control each step separately for advanced use cases. + +## Available Commands + +- **`tx invoke`**: Resolve, sign, and submit a tx3 transaction in one step +- **`tx construct`**: Build transactions interactively or programmatically +- **`tx resolve`**: Resolve a tx3 transaction to CBOR +- **`tx sign`**: Sign a CBOR transaction +- **`tx submit`**: Submit a CBOR transaction to the blockchain + +--- + +## tx invoke + +The `tx invoke` command executes a complete transaction workflow: resolving a tx3 file, signing it with your wallet, and submitting it to the blockchain. + +### Usage + +```bash +cshell tx invoke --tx3-file +``` + +### Options + +Run `cshell tx invoke --help` to see all available options. + +#### Using with Different Provider + +You can specify a different provider using flags: + +```bash +cshell tx invoke --tx3-file ./transfer.tx3 --provider testnet +``` + +### How it Works + +When you run `tx invoke`, Cshell: + +1. **Reads** your tx3 file +2. **Resolves** the transaction via your configured TRP server +3. **Signs** the transaction using the appropriate wallet(s) +4. **Submits** the signed transaction to the blockchain +5. **Returns** the transaction hash + + + +--- + +## tx construct + +The `tx construct` command provides tools for building transactions programmatically or interactively, offering more control than tx3 files for advanced use cases. + +### Available Subcommands + +The construct command includes several subcommands: + +```bash +cshell tx construct --help +``` + +#### Wizard + +Generate a tx3 transaction using an interactive wizard: + +```bash +cshell tx construct wizard +``` + +The wizard guides you through the transaction creation process step by step, asking for inputs, outputs, and other transaction details. + +#### Add Input + +Add a UTxO input to a transaction: + +```bash +cshell tx construct add-input --tx3-file +``` + +**Options:** +- `--tx3-file`: Path to the final tx3 file used to identify current transaction (tx3 file will be created on build command) +- Additional options available with `--help` + +#### Add Output + +Add an output to a transaction: +```bash +cshell tx construct add-output --tx3-file +``` + +**Options:** +- `--tx3-file`: Path to the final tx3 file used to identify current transaction (tx3 file will be created on build command) +- Additional options for assets and datum with `--help` + +#### Build + +Generate the tx3 file from accumulated inputs and outputs: + +```bash +cshell tx construct build --tx3-file +``` + +This will take all the inputs/outputs you've added by add-input / add-output commands and create a complete tx3 file. + +### Workflow Example + +Here's a typical workflow for constructing a transaction manually: + +```bash +# Start with the wizard to set up basic structure +cshell tx construct wizard + +# Or build step by step: + +# 1. Add input UTxO +cshell tx construct add-input --tx3-file my-transaction.tx3 + +# 2. Add output +cshell tx construct add-output --tx3-file my-transaction.tx3 + +# 3. Add change output +cshell tx construct add-output --tx3-file my-transaction.tx3 + +# 4. Build the transaction +cshell tx construct build --tx3-file my-transaction.tx3 +``` + +### Additional Help + +Each subcommand has its own help documentation: + +```bash +cshell tx construct wizard --help +cshell tx construct add-input --help +cshell tx construct add-output --help +cshell tx construct build --help +``` + +--- + +## tx resolve + +The `tx resolve` command converts a tx3 file into CBOR format without signing or submitting it. This is useful for inspecting transaction details or preparing transactions for external signing. + +### Usage + +```bash +cshell tx resolve --tx3-file +``` + +### Options + +Run `cshell tx resolve --help` to see all available options. + +### Examples + +#### Basic Resolution + +```bash +cshell tx resolve --tx3-file ./transfer.tx3 +``` + +This outputs the resolved CBOR transaction to stdout. + + + +#### With Different Provider + +```bash +cshell tx resolve --tx3-file ./transfer.tx3 --provider testnet +``` + +--- + +## tx sign + +The `tx sign` command signs a CBOR transaction with your wallet's private keys, preparing it for submission to the blockchain. + +### Usage + +```bash +cshell tx sign +``` + +### Options + +Run `cshell tx sign --help` to see all available options. + +### Examples + +#### Basic Signing + +```bash +# Sign a resolved transaction +cshell tx sign +``` + +The signed transaction will be output to stdout. + +#### Specify Output File + +```bash +cshell tx sign > signed.cbor +``` + +#### Sign with Specific Wallet + +If you have multiple wallets, specify which one to use: + +```bash +cshell tx sign --signer my-wallet +``` + +### Workflow + +Typically used as part of a manual transaction workflow: + +```bash +# 1. Resolve the transaction +cshell tx resolve --tx3-file transfer.tx3 + +# 2. Sign the transaction +cshell tx sign + +# 3. Submit to blockchain +cshell tx submit +``` + + + +--- + +## tx submit + +The `tx submit` command broadcasts a signed CBOR transaction to the Cardano blockchain via your configured TRP provider. + +### Usage + +```bash +cshell tx submit +``` + +### Options + +Run `cshell tx submit --help` to see all available options. + +### Examples + +#### Basic Submission + +```bash +cshell tx submit +``` + +Successful submission returns a transaction hash: + +``` +Submitted TX: +TX Hash: 8f3a2b1c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2 +``` + +#### With Different Provider + +Submit to a specific provider: + +```bash +cshell tx submit --provider mainnet +``` + +#### Save Transaction Hash + +Capture the transaction hash for later reference: + +```bash +cshell tx submit > tx-submit-result.txt +``` + +### Complete Workflow + +Here's the full manual transaction workflow: + +```bash +# 1. Resolve transaction +cshell tx resolve --tx3-file transfer.tx3 + +# 2. Sign transaction +cshell tx sign + +# 3. Submit to blockchain +cshell tx submit + +# 4. Monitor transaction (using the returned hash) +cshell search transaction +``` + + + +### Verifying Submission + +After submitting, verify your transaction: + +#### Using Cshell Explorer + + + +```bash +# Open explorer and check recent transactions +cshell explorer +``` + +Navigate to the Transactions tab to see your submitted transaction. + +#### Using Search Command + +```bash +# Search by transaction hash +cshell search transaction +``` + +#### Check Wallet Balance + +```bash +# Verify balance changes +cshell wallet balance +``` + +--- + +## Quick Reference + + diff --git a/docs/usage.mdx b/docs/usage.mdx index a1c6194..dbbab9d 100644 --- a/docs/usage.mdx +++ b/docs/usage.mdx @@ -4,49 +4,132 @@ sidebar: order: 3 --- -import { Aside } from '@astrojs/starlight/components'; +import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; -With Cshell fully configured ([provider](/cshell/provider) and [wallet](/cshell/wallet)), it's time to start using it to manage transactions or fetch on-chain data. - -You can execute any transaction in Cshell using [**tx3**](https://docs.txpipe.io/tx3). Cshell will call the TRP to resolve and submit the transaction. +With Cshell fully configured ([provider](/cshell/provider) and [wallet](/cshell/wallet)), you're ready to manage transactions and explore blockchain data. - +## Quick Start -## Commands -The commands below are used to handle transactions and fetch on-chain data. +### Execute a Transaction -### Transactions -You can manage transactions by using Cshell's `tx` or `transactions` commands. To see which tx commands are available, run the following: +The quickest way to submit a transaction is using [**tx3**](https://docs.txpipe.io/tx3) files: ```bash -cshell tx --help +cshell tx invoke --tx3-file ./my-transaction.tx3 ``` -Use the `--help` flag with any command to view its available options. +This will resolve, sign, and submit your transaction in one step. + +### Search Blockchain Data + +Query blocks and transactions: ```bash -cshell tx invoke --help +# Search for a specific block +cshell search block + +# Search for a transaction +cshell search transaction ``` -### Search -You can search by block or transactions using the commands search. To see which search commands are available, run the following +### Explore with GUI - +Launch the terminal-based explorer: ```bash -cshell search --help +cshell explorer ``` -Use the `--help` flag with any command to view its available options. +## Available Commands + +Cshell provides several command groups for different operations: + + + + + + + + + +## Common Workflows + +### Send ADA ```bash -cshell search block --help +# 1. Create a tx3 file describing the transfer +# 2. Execute the transaction +cshell tx invoke --tx3-file ./transfer.tx3 ``` + +### Mint Tokens + +```bash +# 1. Create a tx3 file with minting instructions +# 2. Execute the transaction +cshell tx invoke --tx3-file ./mint-token.tx3 +``` + +### Verify a Transaction + +```bash +# After submission, verify it was confirmed +cshell search transaction +``` + +### Check Wallet Balance + +```bash +cshell wallet balance +``` + +## Output Formats + +Many commands support multiple output formats: + +```bash +# Human-readable table (default) +cshell search block + +# Machine-readable JSON +cshell search block --output-format json +``` + + + +## Next Steps + +- Learn about [transaction commands](/cshell/commands/tx) for managing transactions +- Explore [search commands](/cshell/commands/search) for querying blockchain data +- Check out the [examples](https://github.com/txpipe/cshell/tree/main/examples) for sample tx3 files diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f83a34c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,72 @@ +# TX3 Transaction Examples + +This directory contains example TX3 files to execute through CShell invoke. + +## Prerequisites + +Before running TX3 files, ensure you have: + +1. **CShell installed** - See the main [README](../README.md) for installation instructions +2. **A CShell configuration file** - Created using `cshell provider create` and `cshell wallet create` +3. **At least one provider configured** - For blockchain connectivity +4. **At least one wallet configured** - For signing transactions + +## How to Execute TX3 Files + +Use the following command pattern to execute a TX3 file: + +```bash +cshell -s ./cshell.toml tx invoke --tx3-file ./ +``` + +### Command Breakdown + +- `cshell` - Runs CShell +- `-s ` - Specifies the CShell configuration file path +- `tx invoke` - Invokes a transaction +- `--tx3-file ` - Path to the TX3 file to execute + +### Configuration File + +The `-s` flag points to your CShell configuration file (usually `cshell.toml`), which contains: +- Provider settings +- Wallet configurations + +## Available Examples + +### 1. `transfer.tx3` - Basic ADA Transfer + +Transfers ADA from one party to another with change calculation. + +**Parties**: `Sender`, `Receiver` +**Parameters**: `quantity` (amount to transfer in lovelace) + +```bash +cshell -s ~/.tx3/tmp/devnet_756435378c8d3771/cshell.toml tx invoke --tx3-file ./transfer.tx3 +``` + +### 2. `mint_token.tx3` - Token Minting + +Creates new native tokens on Cardano. + +**Parties**: `Minter` +**Parameters**: +- `token_policy` (policy ID in bytes) +- `token_name` (token name in bytes) +- `quantity` (amount to mint) + +```bash +cshell -s ~/.tx3/tmp/devnet_756435378c8d3771/cshell.toml tx invoke --tx3-file ./mint_token.tx3 +``` + +### 3. `mint_with_script.tx3` - Plutus Script Token Minting + +Demonstrates token minting using a Plutus v3 script with a PIN-based vending machine mechanism. This example shows how to interact with smart contracts for token minting. + +**Parties**: `Customer` +**Parameters**: +- `pin` (PIN code in bytes) + +```bash +cshell -s ~/.tx3/tmp/devnet_756435378c8d3771/cshell.toml tx invoke --tx3-file ./mint_with_script.tx3 +``` diff --git a/examples/mint_token.tx3 b/examples/mint_token.tx3 new file mode 100644 index 0000000..722cdff --- /dev/null +++ b/examples/mint_token.tx3 @@ -0,0 +1,27 @@ +party Minter; + +tx mint_token ( + token_policy: Bytes, + token_name: Bytes, + quantity: Int, +) { + + locals { + token: AnyAsset(token_policy, token_name, quantity), + } + + input source { + from: Minter, + min_amount: fees, + } + + mint { + amount: token, + redeemer: (), + } + + output destination { + to: Minter, + amount: source + token - fees, + } +} \ No newline at end of file diff --git a/examples/mint_with_script.tx3 b/examples/mint_with_script.tx3 new file mode 100644 index 0000000..43ce50f --- /dev/null +++ b/examples/mint_with_script.tx3 @@ -0,0 +1,34 @@ +party Customer; + +policy VendingMachine = 0x3f2e288b4a19ab51d1775d2c6e397b43fad17cc8c47b988603136d73; + +asset COIN = 0x3f2e288b4a19ab51d1775d2c6e397b43fad17cc8c47b988603136d73."COIN"; + +tx claim_tokens( + pin: Bytes, +) { + input gas { + from: Customer, + min_amount: fees, + } + + collateral { + from: Customer, + min_amount: fees, + } + + mint { + amount: COIN(1), + redeemer: pin, + } + + output { + to: Customer, + amount: gas - fees + COIN(1), + } + + cardano::plutus_witness { + version: 3, + script: 0x5903e501010029800aba4aba2aba1aab9faab9eaab9dab9cab9a488888888c96600264653001300900198049805000cc0240092225980099b8748000c024dd500144c966002009007803c01e00f1323233223233225330103372c9210e70726f76696465642070696e3a20003732660046ea40052201001325330113372c92011370726f76696465642070696e20686173683a20003732660066ea400522010013371e0029111c4f43be03892183d6ea41480f616be43b58937aad2c7511e71be9f80d00379000260020026eb8024888c9660020071323233223300a00233714910101280059800800c4cdc52441035b5d2900006899b8a489035b5f20009800800ccdc52441025d2900006914c00402a00530070014029229800805400a002805100920305980099b880014803a266e0120f2010018acc004cdc4000a41000513370066e01208014001480362c80910121bac3015002375a60260026466ec0dd418098009ba73014001375400713259800800c4cdc52441027b7d00003899b8a489037b5f20003232330010010032259800800c400e264b30010018994c00402a6030003337149101023a200098008054c06400600a805100a180d00144ca6002015301800199b8a489023a200098008054c064006600e66008008004805100a180d0012032301a001406066e29220102207d0000340546eac00e264b3001001899b8a489025b5d00003899b8a489035b5f20009800800ccdc52441015d00003914c00401e0053004001401d229800803c00a0028039006202a3758007133006375a0060051323371491102682700329800800ccdc01b8d0024800666e292210127000044004444b3001337100049000440062646645300100699b800054800666e2ccdc00012cc004cdc40012402914818229037202e3371666e000056600266e2000520148a40c11481b9017002200c33706002901019b8600148080cdc70020012028375c00680b8dc5245022c2000223233001001003225980099b8700148002266e292210130000038acc004cdc4000a40011337149101012d0033002002337029000000c4cc014cdc2000a402866e2ccdc019b85001480512060003403880708888c8cc004004014896600200310058992cc004006266008602c00400d1330053016002330030030014054602c00280a0c0040048896600266e2400920008800c6600200733708004900a4cdc599b803370a004900a240c0002801900b201e375c601860146ea800a29410070c024004c014dd500545268a99801a4811856616c696461746f722072657475726e65642066616c7365001365640082a660049201206578706563742070696e3a20427974654172726179203d2072656465656d6572001601, + } +} \ No newline at end of file diff --git a/examples/transfer.tx3 b/examples/transfer.tx3 new file mode 100644 index 0000000..6fb975f --- /dev/null +++ b/examples/transfer.tx3 @@ -0,0 +1,21 @@ +party Sender; +party Receiver; + +tx transfer( + quantity: Int, +) { + input source { + from: Sender, + min_amount: Ada(quantity) + fees, + } + + output destination { + to: Receiver, + amount: Ada(quantity), + } + + output { + to: Sender, + amount: source - Ada(quantity) - fees, + } +} diff --git a/src/tx/construct/add_input.rs b/src/tx/construct/add_input.rs new file mode 100644 index 0000000..fbb3f07 --- /dev/null +++ b/src/tx/construct/add_input.rs @@ -0,0 +1,33 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Result, Context}; +use clap::Parser; +use tracing::instrument; + +#[derive(Parser, Clone)] +pub struct Args { + /// Path for tx3 file to create the transaction + #[arg(long)] + tx3_file: PathBuf, +} + +#[instrument("add-input", skip_all)] +pub async fn run(args: Args, _ctx: &crate::Context) -> Result<()> { + let ast_path_buf = args.tx3_file.with_extension("ast"); + + let mut tx_builder = super::common::TransactionBuilder::from_ast(&ast_path_buf)?; + + tx_builder.collect_inputs(true)?; + + // Serialize and write AST + let ast_json = serde_json::to_string_pretty(&tx_builder.ast) + .context("Failed to serialize tx3 AST")?; + + fs::write(&ast_path_buf, ast_json) + .with_context(|| format!("Failed to write tx3 AST file: {}", ast_path_buf.display()))?; + + println!("\n✅ Input added successfully!"); + println!("📄 File saved to: {}", ast_path_buf.display()); + + Ok(()) +} \ No newline at end of file diff --git a/src/tx/construct/add_output.rs b/src/tx/construct/add_output.rs new file mode 100644 index 0000000..93cda06 --- /dev/null +++ b/src/tx/construct/add_output.rs @@ -0,0 +1,33 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Result, Context}; +use clap::Parser; +use tracing::instrument; + +#[derive(Parser, Clone)] +pub struct Args { + /// Path for tx3 file to create the transaction + #[arg(long)] + tx3_file: PathBuf, +} + +#[instrument("add-output", skip_all)] +pub async fn run(args: Args, _ctx: &crate::Context) -> Result<()> { + let ast_path_buf = args.tx3_file.with_extension("ast"); + + let mut tx_builder = super::common::TransactionBuilder::from_ast(&ast_path_buf)?; + + tx_builder.collect_outputs(true)?; + + // Serialize and write AST + let ast_json = serde_json::to_string_pretty(&tx_builder.ast) + .context("Failed to serialize tx3 AST")?; + + fs::write(&ast_path_buf, ast_json) + .with_context(|| format!("Failed to write tx3 AST file: {}", ast_path_buf.display()))?; + + println!("\n✅ Output added successfully!"); + println!("📄 File saved to: {}", ast_path_buf.display()); + + Ok(()) +} \ No newline at end of file diff --git a/src/tx/construct/build.rs b/src/tx/construct/build.rs new file mode 100644 index 0000000..6f6f36b --- /dev/null +++ b/src/tx/construct/build.rs @@ -0,0 +1,31 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Result, Context}; +use clap::Parser; +use tracing::instrument; + +#[derive(Parser, Clone)] +pub struct Args { + /// Path for tx3 file to create the transaction + #[arg(long)] + tx3_file: PathBuf, +} + +#[instrument("build", skip_all)] +pub async fn run(args: Args, _ctx: &crate::Context) -> Result<()> { + let ast_path_buf = args.tx3_file.with_extension("ast"); + + let tx_builder = super::common::TransactionBuilder::from_ast(&ast_path_buf)?; + + // Generate the tx3 content + let tx3_content = tx_builder.generate_tx3_content(); + + // Write to file + fs::write(&args.tx3_file, tx3_content) + .context("Failed to write tx3 file")?; + + println!("\n✅ Transaction created successfully!"); + println!("📄 File saved to: {}", args.tx3_file.display()); + + Ok(()) +} \ No newline at end of file diff --git a/src/tx/construct/common.rs b/src/tx/construct/common.rs new file mode 100644 index 0000000..efd724d --- /dev/null +++ b/src/tx/construct/common.rs @@ -0,0 +1,308 @@ + + +use std::{fs, path::PathBuf, str::FromStr}; + +use anyhow::{Result, Context}; +use pallas::ledger::addresses::Address; +use serde_json::json; +use inquire::{Text, Confirm}; + +pub struct TransactionBuilder { + pub ast: tx3_lang::ast::Program, + pub def_index: usize, +} + +impl TransactionBuilder { + pub fn new(name: String, mut ast: tx3_lang::ast::Program) -> Result { + let mut def_index = ast.txs.iter().position(|tx| tx.name.value == name); + + if def_index.is_none() { + println!("Creating new transaction: {}", name); + // Create it as JSON and parse it as TxDef + // TODO: Make scope pub in tx3_lang and construct directly or implement `Default` + let value = json!({ + "name": tx3_lang::ast::Identifier::new(name), + "parameters": tx3_lang::ast::ParameterList { + parameters: Vec::new(), + span: tx3_lang::ast::Span::default(), + }, + "references": [], + "inputs": [], + "outputs": [], + "mints": [], + "burns": [], + "adhoc": [], + "span": tx3_lang::ast::Span::default(), + "collateral": [], + }); + let tx_def: tx3_lang::ast::TxDef = + serde_json::from_value(value) + .context("Failed to materialize TxDef from JSON template")?; + + ast.txs.push(tx_def); + + def_index = Some(ast.txs.len() - 1); + } + + Ok(Self { + ast: ast.clone(), + def_index: def_index.unwrap(), + }) + } + + pub fn from_ast(ast_path_buf: &PathBuf) -> Result { + let ast = if ast_path_buf.exists() { + let ast_content = fs::read_to_string(ast_path_buf) + .context("Failed to read existing AST file")?; + + serde_json::from_str(&ast_content) + .context("Failed to parse existing AST file")? + } else { + tx3_lang::ast::Program::default() + }; + + TransactionBuilder::new("new_transaction".to_string(), ast) + } + + pub fn collect_inputs(&mut self, skip_question: bool) -> Result<()> { + println!("\n📥 Transaction Inputs"); + println!("===================="); + + if !skip_question { + let add_inputs = Confirm::new("Do you want to add inputs to your transaction?") + .with_default(true) + .prompt()?; + + if !add_inputs { + return Ok(()); + } + } + + + loop { + let input_name = Text::new("Input name:") + .with_help_message("Enter input name (or 'done' to finish)") + .prompt()?; + + if input_name.eq_ignore_ascii_case("done") { + break; + } + + let mut input_block = tx3_lang::ast::InputBlock { + name: input_name.clone(), + span: tx3_lang::ast::Span::default(), + many: false, + fields: Vec::new(), + }; + + let utxo_ref = Text::new("Utxo Ref:") + .with_help_message("Enter the Utxo for this input (txid#index)") + .prompt()?; + + let parts: Vec<&str> = utxo_ref.split('#').collect(); + if parts.len() != 2 { + println!("Invalid Utxo Ref format. Expected format: txid#index"); + continue; + } + + // input_block.fields.push(tx3_lang::ast::InputBlockField::From( + // tx3_lang::ast::DataExpr::String(tx3_lang::ast::StringLiteral::new(address.to_bech32().unwrap())), + // )); + + let txid = hex::decode(parts[0]) + .context("Invalid txid hex in UTxO reference")?; + + let index = parts[1] + .parse::() + .context("Invalid UTxO index")?; + + input_block.fields.push(tx3_lang::ast::InputBlockField::Ref( + tx3_lang::ast::DataExpr::UtxoRef(tx3_lang::ast::UtxoRef { + txid, + index, + span: tx3_lang::ast::Span::default(), + }), + )); + + let min_amount = Text::new("Minimum amount value:") + .with_default("1000000") + .prompt()?; + + let min_amount_value = min_amount + .parse::() + .context("Invalid minimum amount value")?; + + input_block.fields.push(tx3_lang::ast::InputBlockField::MinAmount( + tx3_lang::ast::DataExpr::StaticAssetConstructor(tx3_lang::ast::StaticAssetConstructor { + amount: Box::new(tx3_lang::ast::DataExpr::Number(min_amount_value)), + span: tx3_lang::ast::Span::default(), + r#type: tx3_lang::ast::Identifier::new("Ada".to_string()), + }) + )); + + self.ast.txs[self.def_index].inputs.push(input_block); + + let add_more = Confirm::new("Add another input?") + .with_default(false) + .prompt()?; + + if !add_more { + break; + } + } + + Ok(()) + } + + pub fn collect_outputs(&mut self, skip_question: bool) -> Result<()> { + println!("\n📤 Transaction Outputs"); + println!("====================="); + + if !skip_question { + let add_outputs = Confirm::new("Do you want to add outputs to your transaction?") + .with_default(true) + .prompt()?; + + if !add_outputs { + return Ok(()); + } + } + + loop { + let has_name = Confirm::new("Does this output have a name?") + .with_default(true) + .prompt()?; + + let output_name = if has_name { + Some(Text::new("Output name:") + .with_help_message("Enter output name") + .prompt()?) + } else { + None + }; + + let mut output_block = tx3_lang::ast::OutputBlock { + name: output_name.as_ref().map(|name| tx3_lang::ast::Identifier::new(name.clone())), + span: tx3_lang::ast::Span::default(), + fields: Vec::new(), + }; + + let to_address = Text::new("To address:") + .with_help_message("Enter the address this output goes to") + .prompt()?; + + // Validate address + let address = Address::from_str(&to_address) + .context("Invalid address")?; + + let bech32 = address + .to_bech32() + .context("Failed to encode bech32 address")?; + + output_block.fields.push(tx3_lang::ast::OutputBlockField::To( + Box::new(tx3_lang::ast::DataExpr::String( + tx3_lang::ast::StringLiteral::new(bech32) + )), + )); + + let amount = Text::new("Amount:") + .with_default("1000000") + .prompt()?; + + let amount_value = amount + .parse::() + .context("Invalid Ada amount")?; + + output_block.fields.push(tx3_lang::ast::OutputBlockField::Amount( + Box::new(tx3_lang::ast::DataExpr::StaticAssetConstructor(tx3_lang::ast::StaticAssetConstructor { + amount: Box::new(tx3_lang::ast::DataExpr::Number(amount_value)), + span: tx3_lang::ast::Span::default(), + r#type: tx3_lang::ast::Identifier::new("Ada".to_string()), + })) + )); + + self.ast.txs[self.def_index].outputs.push(output_block); + + + let add_more = Confirm::new("Add another output?") + .with_default(true) + .prompt()?; + + if !add_more { + break; + } + } + + Ok(()) + } + + pub fn generate_tx3_content(self) -> String { + let mut content = String::new(); + + // Add transaction + content.push_str(&format!("tx {}() {{\n", self.ast.txs[self.def_index].name.value)); + + // Add inputs + for input in &self.ast.txs[self.def_index].inputs { + content.push_str(&format!("\tinput {} {{\n", input.name)); + input.fields.iter().for_each(|field| { + match field { + tx3_lang::ast::InputBlockField::From( + tx3_lang::ast::DataExpr::String(literal) + ) => { + content.push_str(&format!("\t\tfrom: \"{}\",\n", literal.value)); + }, + tx3_lang::ast::InputBlockField::Ref( + tx3_lang::ast::DataExpr::UtxoRef(utxoref) + ) => { + content.push_str(&format!("\t\tref: 0x{}#{},\n", hex::encode(&utxoref.txid), utxoref.index)); + }, + tx3_lang::ast::InputBlockField::MinAmount( + tx3_lang::ast::DataExpr::StaticAssetConstructor(constructor) + ) => { + let amount = match *constructor.amount { + tx3_lang::ast::DataExpr::Number(num) => num.to_string(), + _ => "unknown".to_string(), + }; + content.push_str(&format!("\t\tmin_amount: {}({}),\n", constructor.r#type.value, amount)); + }, + _ => {} + } + }); + content.push_str("\t}\n\n"); + } + + // Add outputs + for output in &self.ast.txs[self.def_index].outputs { + if let Some(name) = &output.name { + content.push_str(&format!("\toutput {} {{\n", name.value)); + } else { + content.push_str("\toutput {\n"); + } + + output.fields.iter().for_each(|field| { + match field { + tx3_lang::ast::OutputBlockField::To(expr) => { + if let tx3_lang::ast::DataExpr::String(literal) = expr.as_ref() { + content.push_str(&format!("\t\tto: \"{}\",\n", literal.value)); + } + }, + tx3_lang::ast::OutputBlockField::Amount(expr) => { + if let tx3_lang::ast::DataExpr::StaticAssetConstructor(constructor) = expr.as_ref() { + let amount = match *constructor.amount { + tx3_lang::ast::DataExpr::Number(num) => num.to_string(), + _ => "unknown".to_string(), + }; + content.push_str(&format!("\t\tamount: {}({}),\n", constructor.r#type.value, amount)); + } + }, + _ => {} + } + }); + content.push_str("\t}\n\n"); + } + + content.push_str("}\n"); + content + } +} diff --git a/src/tx/construct/mod.rs b/src/tx/construct/mod.rs new file mode 100644 index 0000000..9c356de --- /dev/null +++ b/src/tx/construct/mod.rs @@ -0,0 +1,41 @@ +use clap::{Parser, Subcommand}; +use tracing::instrument; + +mod wizard; +mod common; +mod add_input; +mod add_output; +mod build; + +#[derive(Parser)] +pub struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Generate a tx3 transaction using a wizard + Wizard(wizard::Args), + + /// Add input to transaction + #[command(name = "add-input")] + AddInput(add_input::Args), + + /// Add output to transaction + #[command(name = "add-output")] + AddOutput(add_output::Args), + + /// Build the transaction + Build(build::Args), +} + +#[instrument("construct", skip_all)] +pub async fn run(args: Args, ctx: &crate::Context) -> anyhow::Result<()> { + match args.command { + Commands::Wizard(args) => wizard::run(args, ctx).await, + Commands::AddInput(args) => add_input::run(args, ctx).await, + Commands::AddOutput(args) => add_output::run(args, ctx).await, + Commands::Build(args) => build::run(args, ctx).await, + } +} diff --git a/src/tx/construct/wizard.rs b/src/tx/construct/wizard.rs new file mode 100644 index 0000000..5ea5423 --- /dev/null +++ b/src/tx/construct/wizard.rs @@ -0,0 +1,57 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Result, Context}; +use clap::Parser; +use tracing::instrument; +use inquire::Confirm; + +#[derive(Parser, Clone)] +pub struct Args { + /// Path for tx3 file to create the transaction + #[arg(long)] + tx3_file: PathBuf, +} + +#[instrument("wizard", skip_all)] +pub async fn run(args: Args, _ctx: &crate::Context) -> Result<()> { + let ast_path_buf = args.tx3_file.with_extension("ast"); + + if args.tx3_file.exists() { + println!("⚠️ Warning: The specified tx3 file already exists and will be overwritten."); + let proceed = Confirm::new("Do you want to continue?") + .with_default(false) + .prompt()?; + + if !proceed { + println!("Operation cancelled by user."); + return Ok(()); + } + } + + let mut tx_builder = super::common::TransactionBuilder::from_ast(&ast_path_buf)?; + + tx_builder.collect_inputs(false)?; + + tx_builder.collect_outputs(false)?; + + let ast = tx_builder.ast.clone(); + + // Generate the tx3 content + let tx3_content = tx_builder.generate_tx3_content(); + + // Write to file + fs::write(&args.tx3_file, tx3_content) + .context("Failed to write tx3 file")?; + + // Serialize and write AST + let ast_json = serde_json::to_string_pretty(&ast) + .context("Failed to serialize tx3 AST")?; + + fs::write(&ast_path_buf, ast_json) + .with_context(|| format!("Failed to write tx3 AST file: {}", ast_path_buf.display()))?; + + println!("\n✅ Transaction created successfully!"); + println!("📄 File saved to: {}", args.tx3_file.display()); + + Ok(()) +} diff --git a/src/tx/invoke.rs b/src/tx/invoke.rs index 1c0ec3d..a9b9c49 100644 --- a/src/tx/invoke.rs +++ b/src/tx/invoke.rs @@ -91,7 +91,7 @@ pub async fn run(args: Args, ctx: &crate::Context) -> Result<()> { } OutputFormat::Table => { - println!("Tx Hash: {}", hex::encode(&hash)); + println!("Tx Hash: {}", &hash); println!("Tx CBOR: {}", hex::encode(&cbor)); } } diff --git a/src/tx/mod.rs b/src/tx/mod.rs index bef0ea8..da9bd82 100644 --- a/src/tx/mod.rs +++ b/src/tx/mod.rs @@ -7,6 +7,7 @@ mod invoke; mod resolve; mod sign; mod submit; +mod construct; #[derive(Parser)] pub struct Args { @@ -27,6 +28,9 @@ enum Commands { /// Submit a CBOR transaction Submit(submit::Args), + + /// Construct a new transaction using tx3 + Construct(construct::Args), } #[instrument("transaction", skip_all)] @@ -36,5 +40,6 @@ pub async fn run(args: Args, ctx: &crate::Context) -> anyhow::Result<()> { Commands::Resolve(args) => resolve::run(args, ctx).await, Commands::Sign(args) => sign::run(args, ctx).await, Commands::Submit(args) => submit::run(args, ctx).await, + Commands::Construct(args) => construct::run(args, ctx).await, } }