Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ members = [
"util/tracing",
"util/syncador",
"util/collections",
"util/publish-via-explorer",
"util/whitelist",
"networks/movement/*",
"benches/*",
Expand Down
10 changes: 10 additions & 0 deletions util/publish-via-explorer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "publish-via-explorer"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
6 changes: 6 additions & 0 deletions util/publish-via-explorer/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "hello"
version = "0.0.1"

[addresses]
hello = "_"
62 changes: 62 additions & 0 deletions util/publish-via-explorer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# publish-via-explorer

This tool prepares a Move package for publishing using the Movement Explorer UI.

It compiles the embedded Move package and outputs the required `arg0` and `arg1` values for the `publish_package_txn` function:

[https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish\_package\_txn?network=mainnet](https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish_package_txn?network=mainnet)

## Function Signature

```move
entry fun publish_package_txn(
owner: &signer,
metadata_serialized: vector<u8>,
code: vector<vector<u8>>
)
```

## Usage

1. Ensure the `movement` CLI is installed and available in your `PATH`.

2. Run from the workspace root:

```bash
cargo run -p publish-via-explorer
```

3. The tool will compile the embedded Move package and output two formatted arguments:

* `arg0` (vector<u8>)
* `arg1` (vector\<vector<u8>>)

It will also write these to:

```
publish-via-explorer/build/hello/explorer_payload.log
```

## Submitting via the Explorer UI

1. Open the following link (for mainnet publishing... you can switch to testnet if you prefer):

[https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish\_package\_txn?network=mainnet](https://explorer.movementlabs.xyz/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/run/code/publish_package_txn?network=mainnet)

2. Input the args:

* **signer**: your account address (must be funded)
* **arg0**: the full vector<u8> array (surrounded by brackets)
* **arg1**: the full vector\<vector<u8>> array (outer and inner brackets must be present)

Example:

```json
arg0: [5,104,101,108,108,111,...]
arg1: [[161,28,235,11,...]]
```
> [!TIP] Be sure to connect to the explorer with the same wallet that you used in your local Movement config.

After successful publishing via explorer, you will see a successful message as follows:

<img width="1205" alt="image" src="https://github.com/user-attachments/assets/312c2c17-e164-45d8-a7ca-c379ef0f21ed" />
5 changes: 5 additions & 0 deletions util/publish-via-explorer/sources/hello.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module hello::hello {
public entry fun hi() {
// no-op
}
}
118 changes: 118 additions & 0 deletions util/publish-via-explorer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use anyhow::{Context, Result};
use std::{
env,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
process::Command,
};

fn read_bytes(path: &Path) -> Result<Vec<u8>> {
Ok(fs::read(path)?)
}

fn format_vector_u8(data: &[u8]) -> String {
format!("[{}]", data.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(","))
}

fn format_vector_vector_u8(data: &[Vec<u8>]) -> String {
serde_json::to_string(data).expect("json encode failed")
}

fn address_from_config() -> Result<String> {
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let config_path = crate_dir.join(".movement/config.yaml");
let contents = fs::read_to_string(&config_path)
.with_context(|| format!("failed to read {}", config_path.display()))?;

let config: serde_yaml::Value = serde_yaml::from_str(&contents)?;
let default = config
.get("profiles")
.and_then(|p| p.get("default"))
.context("missing [profiles][default] in config")?;

let addr = default
.get("account")
.or_else(|| default.get("address"))
.context("missing 'account' or 'address' under [profiles][default]")?
.as_str()
.context("address is not a string")?;

Ok(addr.to_string())
}

pub fn run() -> Result<()> {
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let address = address_from_config()?;

let move_compile = Command::new("movement")
.args([
"move",
"compile",
"--named-addresses",
&format!("hello={}", address),
"--save-metadata",
"--package-dir",
".",
])
.current_dir(&crate_dir)
.status()
.context("failed to run movement move compile")?;

if !move_compile.success() {
anyhow::bail!("Move compilation failed");
}

let build_dir = crate_dir.join("build/hello");
let metadata_path = build_dir.join("package-metadata.bcs");
let modules_dir = build_dir.join("bytecode_modules");

let metadata = read_bytes(&metadata_path)?;
let mut modules = Vec::new();
for entry in fs::read_dir(&modules_dir)? {
let entry = entry?;
if entry.path().extension().map(|ext| ext == "mv").unwrap_or(false) {
modules.push(read_bytes(&entry.path())?);
}
}

let arg0 = format_vector_u8(&metadata);
let arg1 = format_vector_vector_u8(&modules);

let log_path = build_dir.join("explorer_payload.log");
let mut file = File::create(&log_path)?;
writeln!(file, "arg0 (vector<u8>):\n{}\n", arg0)?;
writeln!(file, "arg1 (vector<vector<u8>>):\n{}\n", arg1)?;

println!("\n----- COPY INTO EXPLORER -----\n");
println!("arg0 (vector<u8>):\n{}", arg0);
println!("\narg1 (vector<vector<u8>>):\n{}", arg1);
println!("\n(Log saved to {})", log_path.display());

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_format_vector_u8() {
let input = vec![1, 2, 3, 4, 255];
let output = format_vector_u8(&input);
assert_eq!(output, "[1,2,3,4,255]");
}

#[test]
fn test_format_vector_vector_u8() {
let input = vec![vec![1, 2, 3], vec![4, 5, 6]];
let output = format_vector_vector_u8(&input);
assert_eq!(output, "[[1,2,3],[4,5,6]]");
}

#[test]
fn test_read_bytes_failure() {
let result = read_bytes(Path::new("nonexistent.file"));
assert!(result.is_err());
}
}
3 changes: 3 additions & 0 deletions util/publish-via-explorer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() -> anyhow::Result<()> {
publish_via_explorer::run()
}
Loading