
Relay
- 9 installs
- 12 repo stars
- Updated August 3, 2026
- okx/plugin-store
Helps with ai & agent building tasks during AI-assisted development.
About
Relay is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- Relay
- AI & Agent Building
- AI-coding skill
Relay by the numbers
- 9 all-time installs (skills.sh)
- Ranked #12,133 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/okx/plugin-store --skill relayAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 12 |
| Last updated | August 3, 2026 |
| Repository | okx/plugin-store ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Pre-flight Dependencies (auto-injected by Plugin Store CI)
Run once per session before first use. These checks ensure required tools are installed.
Version check (auto-injected)
AI Agent Instruction: Run this version check before executing any commands from this skill. The check is a read-only fetch fromraw.githubusercontent.com— it does not install anything. If it reportsUpdate available, you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggestednpx skills add …command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding.
# Check for skill updates (1-hour cache).
# Network scope: this block calls raw.githubusercontent.com for the version probe only.
# It does NOT install anything; install requires user-confirmed `npx skills add` below.
UPDATE_CACHE="$HOME/.plugin-store/update-cache/relay-plugin"
CACHE_MAX=3600
LOCAL_VER="0.1.1"
DO_CHECK=true
if [ -f "$UPDATE_CACHE" ]; then
CACHE_MOD=$(stat -f %m "$UPDATE_CACHE" 2>/dev/null || stat -c %Y "$UPDATE_CACHE" 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$(( NOW - CACHE_MOD ))
[ "$AGE" -lt "$CACHE_MAX" ] && DO_CHECK=false
fi
if [ "$DO_CHECK" = true ]; then
REMOTE_VER=$(curl -sf --max-time 3 "https://raw.githubusercontent.com/okx/plugin-store/main/skills/relay-plugin/plugin.yaml" | grep '^version' | head -1 | tr -d '"' | awk '{print $2}')
if [ -n "$REMOTE_VER" ]; then
mkdir -p "$HOME/.plugin-store/update-cache"
echo "$REMOTE_VER" > "$UPDATE_CACHE"
fi
fi
REMOTE_VER=$(cat "$UPDATE_CACHE" 2>/dev/null || echo "$LOCAL_VER")
if [ "$REMOTE_VER" != "$LOCAL_VER" ]; then
echo "Update available: relay-plugin v$LOCAL_VER -> v$REMOTE_VER."
echo "ACTION REQUIRED: ask the user to confirm before running:"
echo " npx skills add okx/plugin-store --skill relay-plugin --global"
echo "(This contacts the npm registry and github.com/okx/plugin-store and overwrites this skill. Do NOT auto-run.)"
fiInstall onchainos CLI + Skills (auto-injected)
# 1. Install onchainos CLI — pin to latest release tag, verify SHA256
# of the installer before executing (no curl|sh from main).
if ! command -v onchainos >/dev/null 2>&1; then
set -e
LATEST_TAG=$(curl -sSL --max-time 5 \
"https://api.github.com/repos/okx/onchainos-skills/releases/latest" \
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$LATEST_TAG" ]; then
echo "ERROR: failed to resolve latest onchainos release tag (network or rate limit)." >&2
echo " Manual install: https://github.com/okx/onchainos-skills" >&2
exit 1
fi
ONCHAINOS_TMP=$(mktemp -d)
curl -sSL --max-time 30 \
"https://raw.githubusercontent.com/okx/onchainos-skills/${LATEST_TAG}/install.sh" \
-o "$ONCHAINOS_TMP/install.sh"
curl -sSL --max-time 30 \
"https://github.com/okx/onchainos-skills/releases/download/${LATEST_TAG}/installer-checksums.txt" \
-o "$ONCHAINOS_TMP/installer-checksums.txt"
EXPECTED=$(awk '$2 ~ /install\.sh$/ {print $1; exit}' "$ONCHAINOS_TMP/installer-checksums.txt")
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$ONCHAINOS_TMP/install.sh" | awk '{print $1}')
else
ACTUAL=$(shasum -a 256 "$ONCHAINOS_TMP/install.sh" | awk '{print $1}')
fi
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ERROR: onchainos installer SHA256 mismatch — refusing to execute." >&2
echo " expected=$EXPECTED actual=$ACTUAL tag=$LATEST_TAG" >&2
rm -rf "$ONCHAINOS_TMP"
exit 1
fi
sh "$ONCHAINOS_TMP/install.sh"
rm -rf "$ONCHAINOS_TMP"
set +e
fi
# 2. Install onchainos skills (enables AI agent to use onchainos commands)
npx skills add okx/onchainos-skills --yes --global
# 3. Install plugin-store skills (enables plugin discovery and management)
npx skills add okx/plugin-store --skill plugin-store --yes --globalInstall relay-plugin binary + launcher (auto-injected)
# Install shared infrastructure (launcher + update checker, only once)
LAUNCHER="$HOME/.plugin-store/launcher.sh"
CHECKER="$HOME/.plugin-store/update-checker.py"
if [ ! -f "$LAUNCHER" ]; then
mkdir -p "$HOME/.plugin-store"
curl -fsSL "https://raw.githubusercontent.com/okx/plugin-store/main/scripts/launcher.sh" -o "$LAUNCHER" 2>/dev/null || true
chmod +x "$LAUNCHER"
fi
if [ ! -f "$CHECKER" ]; then
curl -fsSL "https://raw.githubusercontent.com/okx/plugin-store/main/scripts/update-checker.py" -o "$CHECKER" 2>/dev/null || true
fi
# Clean up old installation
rm -f "$HOME/.local/bin/relay-plugin" "$HOME/.local/bin/.relay-plugin-core" 2>/dev/null
# Download binary
OS=$(uname -s | tr A-Z a-z)
ARCH=$(uname -m)
EXT=""
case "${OS}_${ARCH}" in
darwin_arm64) TARGET="aarch64-apple-darwin" ;;
darwin_x86_64) TARGET="x86_64-apple-darwin" ;;
linux_x86_64) TARGET="x86_64-unknown-linux-musl" ;;
linux_i686) TARGET="i686-unknown-linux-musl" ;;
linux_aarch64) TARGET="aarch64-unknown-linux-musl" ;;
linux_armv7l) TARGET="armv7-unknown-linux-musleabihf" ;;
mingw*_x86_64|msys*_x86_64|cygwin*_x86_64) TARGET="x86_64-pc-windows-msvc"; EXT=".exe" ;;
mingw*_i686|msys*_i686|cygwin*_i686) TARGET="i686-pc-windows-msvc"; EXT=".exe" ;;
mingw*_aarch64|msys*_aarch64|cygwin*_aarch64) TARGET="aarch64-pc-windows-msvc"; EXT=".exe" ;;
esac
mkdir -p ~/.local/bin
# Download binary + checksums to a sandbox, verify SHA256 before installing.
# Fail-closed: any mismatch / missing checksum entry refuses the install.
# Matches the producer-side workflow at
# .github/workflows/plugin-publish.yml which uploads `checksums.txt`
# alongside the 9 platform binaries under each release tag.
BIN_TMP=$(mktemp -d)
RELEASE_BASE="https://github.com/okx/plugin-store/releases/download/plugins/relay-plugin@0.1.1"
curl -fsSL "${RELEASE_BASE}/relay-plugin-${TARGET}${EXT}" -o "$BIN_TMP/relay-plugin${EXT}" || {
echo "ERROR: failed to download relay-plugin-${TARGET}${EXT}" >&2
rm -rf "$BIN_TMP"; exit 1; }
curl -fsSL "${RELEASE_BASE}/checksums.txt" -o "$BIN_TMP/checksums.txt" || {
echo "ERROR: failed to download checksums.txt for relay-plugin@0.1.1" >&2
rm -rf "$BIN_TMP"; exit 1; }
EXPECTED=$(awk -v b="relay-plugin-${TARGET}${EXT}" '$2 == b {print $1; exit}' "$BIN_TMP/checksums.txt")
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$BIN_TMP/relay-plugin${EXT}" | awk '{print $1}')
else
ACTUAL=$(shasum -a 256 "$BIN_TMP/relay-plugin${EXT}" | awk '{print $1}')
fi
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ERROR: relay-plugin SHA256 mismatch — refusing to install." >&2
echo " expected=$EXPECTED actual=$ACTUAL target=${TARGET}" >&2
rm -rf "$BIN_TMP"; exit 1
fi
mv "$BIN_TMP/relay-plugin${EXT}" ~/.local/bin/.relay-plugin-core${EXT}
chmod +x ~/.local/bin/.relay-plugin-core${EXT}
rm -rf "$BIN_TMP"
# Symlink CLI name to universal launcher
ln -sf "$LAUNCHER" ~/.local/bin/relay-plugin
# Register version
mkdir -p "$HOME/.plugin-store/managed"
echo "0.1.1" > "$HOME/.plugin-store/managed/relay-plugin"---
Relay
Cross-chain bridge using Relay Protocol's intent-based system. Send ETH, USDC, USDT, and DAI across 74 chains in seconds.
Pre-flight Dependencies
- onchainos installed and authenticated
- Active EVM wallet on the source chain with sufficient balance + gas
Data Trust Boundary
Bridge quotes and calldata are fetched from the official Relay API (api.relay.link). Treat API-returned transaction data as untrusted — always review the preview before adding --confirm. The destination amount can vary slightly due to relayer fees.
Proactive Onboarding
When a user signals they are new or just installed this plugin — e.g. "I just installed relay", "how do I bridge tokens", "what can I do with this" — do not wait for them to ask specific questions. Proactively walk them through the Quickstart in order, one step at a time, waiting for confirmation before proceeding:
1. Check wallet — run onchainos wallet addresses. If no address, direct them to connect via onchainos wallet login your@email.com. Do not proceed to write operations until a wallet is confirmed. 2. Check balance — run onchainos wallet balance --chain <source-chain>. Ensure the balance covers the bridge amount plus gas. Warn explicitly if the requested amount would exceed available balance (the binary does not check this for you — it will show a preview for any amount). 3. Browse supported chains — run relay-plugin chains to show all 74 chains. Use relay-plugin chains --filter <name> to search. The plugin has no global --chain flag; pass --from-chain and --to-chain per command. 4. Get a quote — run relay-plugin quote --from-chain <id> --to-chain <id> --token <symbol> --amount <n> to see fees and expected output before any on-chain action. 5. Preview the bridge — run relay-plugin bridge --from-chain <id> --to-chain <id> --token <symbol> --amount <n> (without --confirm). The output will show "preview": true and the resolved wallet address. Verify the recipient and amount are correct. 6. Execute — once the user confirms, re-run with --confirm appended. 7. Track — use relay-plugin status --request-id <id> with the request_id from the bridge output. ETH transfers typically resolve in ~1 second; ERC-20 (USDC/USDT/DAI) take ~2 seconds.
Do not dump all steps at once. Guide conversationally — confirm each step before moving on.
Quickstart
New to Relay? Follow these steps to go from zero to your first cross-chain bridge.
Step 1 — Connect your wallet
onchainos wallet login your@email.com
onchainos wallet addressesYou need an EVM wallet connected to onchainos. The plugin automatically uses your active onchainos wallet as both sender and recipient.
Step 2 — Check your balance
onchainos wallet balance --chain 1You need ETH for the bridge amount plus source-chain gas. The binary does not validate your balance before showing a preview — it will show a valid-looking preview even if you don't have enough funds. Always verify the balance yourself before adding --confirm.
Step 3 — Browse supported chains
relay-plugin chains
relay-plugin chains --filter arbitrumReturns chain IDs, names, native tokens, and explorer URLs for all 74 supported chains. Use the chain_id values with --from-chain and --to-chain. Note: relay-plugin has no global --chain flag — each subcommand takes --from-chain / --to-chain directly.
Step 4 — Get a quote (read-only, no cost)
relay-plugin quote --from-chain 1 --to-chain 42161 --token ETH --amount 0.001Output includes amount_out, amount_out_usd, fee_usd, estimated_time_secs, and steps. ETH bridges require 1 step (deposit). ERC-20 tokens (USDC, USDT, DAI) require 2 steps (approve, deposit). You can also bridge to a different token: add --to-token USDC to receive USDC on the destination chain.
Step 5 — Preview the bridge (no tx sent)
relay-plugin bridge --from-chain 1 --to-chain 42161 --token ETH --amount 0.001Output shows "preview": true, resolved wallet, recipient, human-readable amount_in/amount_out, and a hint message with the exact relay-plugin status command to use after execution. No on-chain action until --confirm is added.
Step 6 — Execute the bridge
relay-plugin bridge --from-chain 1 --to-chain 42161 --token ETH --amount 0.001 --confirmExpected output: "ok": true, "txs": [{"step": "deposit", "tx_hash": "0x..."}], and a track field with the ready-to-run status command. ETH typically arrives on the destination chain within 1–2 seconds.
For ERC-20 tokens, an approval tx fires first, then the deposit. Both tx hashes appear in txs.
Step 7 — Track transfer status
relay-plugin status --request-id <request_id_from_bridge_output>Status values: unknown (not yet indexed — wait a few seconds), pending (in-flight), success (delivered), failed. On success, dest_tx contains the destination-chain tx hash.
---
Overview
Relay Protocol uses an intent-based bridge system. When you call bridge --confirm: 1. Your funds are sent to a Relay solver contract on the source chain. 2. A relayer detects the intent and delivers funds on the destination chain — typically within seconds. 3. For ERC-20 tokens (USDC, USDT, DAI), an approval transaction is sent first, then the deposit.
Supported Chains (74)
Common chains:
| Chain | ID |
|---|---|
| Ethereum | 1 |
| Arbitrum | 42161 |
| Base | 8453 |
| Optimism | 10 |
| Polygon | 137 |
| BNB Chain | 56 |
| zkSync Era | 324 |
| Linea | 59144 |
| Scroll | 534352 |
Run relay-plugin chains for the full list of 74 supported chains.
Commands
chains — List supported chains
relay-plugin chains [--filter <name-or-id>]| Flag | Description |
|---|---|
--filter | Optional filter by chain name or ID |
Returns all active chains with their IDs, names, and native tokens.
---
quote — Get a bridge quote (read-only)
relay-plugin quote \
--from-chain 1 \
--to-chain 42161 \
--token ETH \
--amount 0.01 \
[--to-token ETH]| Flag | Description |
|---|---|
--from-chain | Source chain ID |
--to-chain | Destination chain ID |
--token | Token to send (symbol or address) |
--amount | Amount in human-readable form |
--to-token | Destination token (default: same as --token) |
Output includes amount_out, amount_out_usd, estimated_time_secs, steps, and request_id.
---
bridge — Execute a cross-chain transfer
relay-plugin bridge \
--from-chain 1 \
--to-chain 42161 \
--token ETH \
--amount 0.01 \
[--to-token ETH] \
[--recipient 0x...] \
[--confirm] \
[--dry-run]| Flag | Description |
|---|---|
--from-chain | Source chain ID |
--to-chain | Destination chain ID |
--token | Token to send (symbol or address) |
--amount | Amount in human-readable form |
--to-token | Destination token (default: same as --token) |
--recipient | Destination wallet (default: your wallet) |
Execution modes:
| Mode | Command | What happens |
|---|---|---|
| Preview | (no flags) | Shows expected output and steps; no tx |
| Dry-run | --dry-run | Builds calldata; no onchainos call |
| Execute | --confirm | Approves (ERC-20 only) + broadcasts deposit tx |
For ERC-20 tokens (USDC, USDT, DAI), an exact-amount approval transaction is sent before the deposit. Each bridge sends a fresh approval because the allowance is consumed by the deposit.
---
status — Check transfer status
relay-plugin status --request-id 0x<id>| Flag | Description |
|---|---|
--request-id | Request ID from a previous bridge command |
Status values: unknown (not yet indexed), pending (in-flight), success (delivered), failed.
---
Known Token Symbols
Symbols can be used instead of addresses:
| Symbol | Ethereum | Arbitrum | Base | Optimism | Polygon |
|---|---|---|---|---|---|
| ETH (native) | ✓ | ✓ | ✓ | ✓ | ✓ |
| USDC | ✓ | ✓ | ✓ | ✓ | ✓ |
| USDT | ✓ | ✓ | ✓ | ✓ | ✓ |
| DAI | ✓ | ✓ | ✓ | ✓ | ✓ |
Use the full token address for any token not listed above.
{
"name": "relay-plugin",
"version": "0.1.1",
"description": "Fast cross-chain transfers using Relay Protocol's intent-based bridge",
"binary": "relay",
"chain": "multi"
}# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "hyper"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
name = "icu_collections"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
"utf8_iter",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"serde",
"serde_core",
]
[[package]]
name = "ipnet"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "iri-string"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "native-tls"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "openssl"
version = "0.10.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "potential_utf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "relay-plugin"
version = "0.1.1"
dependencies = [
"anyhow",
"clap",
"reqwest",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "tinystr"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tokio"
version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "web-sys"
version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[package]
name = "relay-plugin"
version = "0.1.1"
edition = "2021"
[[bin]]
name = "relay-plugin"
path = "src/main.rs"
[dependencies]
clap = { version = "4", features = ["derive"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
MIT License
Copyright (c) 2026 skylavis-sky
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
schema_version: 1
name: relay-plugin
version: "0.1.1"
description: "Fast cross-chain transfers using Relay Protocol's intent-based bridge"
author:
name: skylavis-sky
github: skylavis-sky
category: dapp
tags:
- bridge
- cross-chain
- relay
- fast-bridge
- intent
license: MIT
components:
skill:
dir: .
build:
lang: rust
binary_name: relay-plugin
api_calls:
- "https://api.relay.link"
use anyhow::Context;
use serde::{Deserialize, Serialize};
const BASE_URL: &str = "https://api.relay.link";
// Native ETH address used by Relay
pub const NATIVE_ETH: &str = "0x0000000000000000000000000000000000000000";
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RelayChain {
pub id: u64,
pub name: String,
pub display_name: Option<String>,
pub explorer_url: Option<String>,
pub disabled: Option<bool>,
pub currency: Option<ChainCurrency>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChainCurrency {
pub symbol: String,
pub decimals: Option<u8>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct QuoteRequest {
pub user: String,
pub recipient: String,
pub origin_chain_id: u64,
pub destination_chain_id: u64,
pub origin_currency: String,
pub destination_currency: String,
pub amount: String,
pub trade_type: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct QuoteResponse {
pub steps: Vec<Step>,
pub fees: Option<serde_json::Value>,
pub details: Option<Details>,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Step {
pub id: String, // "approve" or "deposit"
pub action: Option<String>,
pub description: Option<String>,
pub kind: Option<String>,
pub items: Vec<StepItem>,
pub request_id: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StepItem {
pub status: Option<String>,
pub data: StepData,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StepData {
pub from: Option<String>,
pub to: String,
pub data: String,
pub value: Option<String>,
pub chain_id: Option<u64>,
pub gas: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Details {
pub currency_in: Option<CurrencyDetail>,
pub currency_out: Option<CurrencyDetail>,
pub time_estimate: Option<u64>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CurrencyDetail {
pub currency: Option<CurrencyInfo>,
pub amount: Option<String>,
pub amount_formatted: Option<String>,
pub amount_usd: Option<String>,
pub minimum_amount: Option<String>,
pub minimum_amount_formatted: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CurrencyInfo {
pub symbol: Option<String>,
pub name: Option<String>,
pub decimals: Option<u8>,
pub address: Option<String>,
pub chain_id: Option<u64>,
}
#[derive(Debug, Deserialize)]
pub struct StatusResponse {
pub status: String,
#[serde(rename = "inTxHashes")]
pub in_tx_hashes: Option<Vec<String>>,
#[serde(rename = "txHashes")]
pub tx_hashes: Option<Vec<String>>,
pub error: Option<String>,
}
pub async fn get_chains() -> anyhow::Result<Vec<RelayChain>> {
let client = reqwest::Client::new();
let resp = client
.get(format!("{}/chains", BASE_URL))
.send().await
.context("Failed to reach api.relay.link/chains")?;
let body: serde_json::Value = resp.json().await?;
// Response is either an array directly or {chains: [...]}
let arr = if body.is_array() {
body
} else {
body.get("chains").cloned().unwrap_or(serde_json::json!([]))
};
let chains: Vec<RelayChain> = serde_json::from_value(arr)
.context("Failed to parse chains response")?;
Ok(chains)
}
pub async fn get_quote(req: QuoteRequest) -> anyhow::Result<QuoteResponse> {
let client = reqwest::Client::new();
let resp = client
.post(format!("{}/quote", BASE_URL))
.json(&req)
.send().await
.context("Failed to reach api.relay.link/quote")?;
let status = resp.status();
let body: serde_json::Value = resp.json().await?;
if !status.is_success() {
let msg = body.get("message")
.or_else(|| body.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("Unknown error from Relay API");
anyhow::bail!("Relay API error ({}): {}", status, msg);
}
serde_json::from_value(body).context("Failed to parse quote response")
}
pub async fn get_status(request_id: &str) -> anyhow::Result<StatusResponse> {
let client = reqwest::Client::new();
let resp = client
.get(format!("{}/intents/status", BASE_URL))
.query(&[("requestId", request_id)])
.send().await
.context("Failed to reach api.relay.link/intents/status")?;
resp.json::<StatusResponse>().await
.context("Failed to parse status response")
}
/// Resolve token symbol to address for a given chain.
/// Returns the address if recognised, otherwise treats the input as an address directly.
pub fn resolve_token(symbol_or_addr: &str, chain_id: u64) -> String {
match symbol_or_addr.to_uppercase().as_str() {
"ETH" | "WETH" => NATIVE_ETH.to_string(),
"USDC" => match chain_id {
1 => "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".to_string(),
42161 => "0xaf88d065e77c8cc2239327c5edb3a432268e5831".to_string(),
8453 => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913".to_string(),
10 => "0x0b2c639c533813f4aa9d7837caf62653d097ff85".to_string(),
137 => "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359".to_string(),
_ => symbol_or_addr.to_string(),
},
"USDT" => match chain_id {
1 => "0xdac17f958d2ee523a2206206994597c13d831ec7".to_string(),
42161 => "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9".to_string(),
8453 => "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2".to_string(),
10 => "0x94b008aa00579c1307b0ef2c499ad98a8ce58e58".to_string(),
137 => "0xc2132d05d31c914a87c6611c10748aeb04b58e8f".to_string(),
_ => symbol_or_addr.to_string(),
},
"DAI" => match chain_id {
1 => "0x6b175474e89094c44da98b954eedeac495271d0f".to_string(),
42161 => "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1".to_string(),
8453 => "0x50c5725949a6f0c72e6c4a641f24049a917db0cb".to_string(),
10 => "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1".to_string(),
137 => "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063".to_string(),
_ => symbol_or_addr.to_string(),
},
_ => symbol_or_addr.to_string(),
}
}
/// Return a public RPC URL for a given chain ID.
fn rpc_url_for_chain(chain_id: u64) -> &'static str {
match chain_id {
1 => "https://eth.llamarpc.com",
42161 => "https://arb1.arbitrum.io/rpc",
8453 => "https://mainnet.base.org",
10 => "https://mainnet.optimism.io",
137 => "https://polygon-rpc.com",
_ => "https://eth.llamarpc.com",
}
}
/// Return a block-explorer tx URL for a given chain ID and tx hash.
pub fn explorer_tx_url(chain_id: u64, tx_hash: &str) -> String {
let base = match chain_id {
1 => "https://etherscan.io/tx/",
42161 => "https://arbiscan.io/tx/",
8453 => "https://basescan.org/tx/",
10 => "https://optimistic.etherscan.io/tx/",
137 => "https://polygonscan.com/tx/",
_ => "https://etherscan.io/tx/",
};
format!("{}{}", base, tx_hash)
}
/// Fetch native ETH balance for an address on a chain (returns raw wei as u128).
/// Returns 0 on any error so a failed balance fetch doesn't block the bridge preview.
pub async fn get_eth_balance(chain_id: u64, address: &str) -> u128 {
let rpc = rpc_url_for_chain(chain_id);
let client = reqwest::Client::new();
let body = serde_json::json!({
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": [address, "latest"],
"id": 1
});
let Ok(resp) = client.post(rpc).json(&body).send().await else { return 0; };
let Ok(val) = resp.json::<serde_json::Value>().await else { return 0; };
let hex = val["result"].as_str().unwrap_or("0x0");
u128::from_str_radix(hex.trim_start_matches("0x"), 16).unwrap_or(0)
}
pub fn token_symbol(addr: &str, _chain_id: u64) -> &'static str {
match addr.to_lowercase().as_str() {
"0x0000000000000000000000000000000000000000" => "ETH",
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" => "USDC", // ETH
"0xaf88d065e77c8cc2239327c5edb3a432268e5831" => "USDC", // ARB
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" => "USDC", // Base
"0x0b2c639c533813f4aa9d7837caf62653d097ff85" => "USDC", // OP
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" => "USDC", // Polygon
"0xdac17f958d2ee523a2206206994597c13d831ec7" => "USDT", // ETH
"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9" => "USDT", // ARB
"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2" => "USDT", // Base
"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58" => "USDT", // OP
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f" => "USDT", // Polygon
"0x6b175474e89094c44da98b954eedeac495271d0f" => "DAI", // ETH
"0xda10009cbd5d07dd0cecc66161fc93d7c9000da1" => "DAI", // ARB/OP
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb" => "DAI", // Base
"0x8f3cf7ad23cd3cadbd9735aff958023239c6a063" => "DAI", // Polygon
_ => "UNKNOWN",
}
}
use clap::Args;
use tokio::time::{sleep, Duration};
use crate::api::{get_quote, get_eth_balance, explorer_tx_url, resolve_token, token_symbol, QuoteRequest, NATIVE_ETH};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
#[derive(Args)]
pub struct BridgeArgs {
/// Source chain ID (e.g. 1 for Ethereum, 42161 for Arbitrum)
#[arg(long)]
pub from_chain: u64,
/// Destination chain ID
#[arg(long)]
pub to_chain: u64,
/// Token to send (symbol or address, e.g. ETH, USDC, 0x...)
#[arg(long, default_value = "ETH")]
pub token: String,
/// Amount to send in human-readable form (e.g. 0.01 for 0.01 ETH)
#[arg(long)]
pub amount: String,
/// Destination token (defaults to same as --token)
#[arg(long)]
pub to_token: Option<String>,
/// Recipient address on destination chain (defaults to your wallet)
#[arg(long)]
pub recipient: Option<String>,
/// Broadcast the bridge transaction. Without this flag, prints a preview only.
#[arg(long)]
pub confirm: bool,
/// Build calldata without calling onchainos (dry-run)
#[arg(long, conflicts_with = "confirm")]
pub dry_run: bool,
}
pub fn parse_amount(s: &str, decimals: u8) -> anyhow::Result<u128> {
if s == "0" || s.is_empty() {
anyhow::bail!("Amount must be greater than 0");
}
let (whole, frac) = if let Some(dot) = s.find('.') {
let w: u128 = s[..dot].parse().map_err(|_| anyhow::anyhow!("Invalid amount: '{}'", s))?;
let frac_str = &s[dot + 1..];
if frac_str.len() > decimals as usize {
anyhow::bail!("Amount '{}' has {} decimal places but token supports only {}", s, frac_str.len(), decimals);
}
let padded = format!("{:0<width$}", frac_str, width = decimals as usize);
let f: u128 = padded.parse().map_err(|_| anyhow::anyhow!("Invalid amount: '{}'", s))?;
(w, f)
} else {
let w: u128 = s.parse().map_err(|_| anyhow::anyhow!("Invalid amount: '{}'", s))?;
(w, 0u128)
};
let scale = 10u128.pow(decimals as u32);
Ok(whole * scale + frac)
}
fn format_value(raw: u128, decimals: u8) -> String {
let scale = 10u128.pow(decimals as u32);
let whole = raw / scale;
let frac = raw % scale;
if frac == 0 {
whole.to_string()
} else {
let frac_str = format!("{:0>width$}", frac, width = decimals as usize);
format!("{}.{}", whole, frac_str.trim_end_matches('0'))
}
}
pub async fn run(args: BridgeArgs) -> anyhow::Result<()> {
let origin_token = resolve_token(&args.token, args.from_chain);
let dest_token_input = args.to_token.as_deref().unwrap_or(&args.token);
let dest_token = resolve_token(dest_token_input, args.to_chain);
let decimals: u8 = if origin_token == NATIVE_ETH { 18 }
else { match token_symbol(&origin_token, args.from_chain) {
"USDC" | "USDT" => 6,
_ => 18,
}};
let amount_raw = parse_amount(&args.amount, decimals)?;
if amount_raw == 0 {
anyhow::bail!("Amount must be greater than 0");
}
let wallet = if args.dry_run {
// Resolve for display only; fall back to placeholder string
resolve_wallet(args.from_chain)
.unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string())
} else if args.confirm {
// Wallet required for actual broadcast
resolve_wallet(args.from_chain)?
} else {
// Preview only — try to resolve but fall back to zero address for quote API
resolve_wallet(args.from_chain)
.unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string())
};
let recipient = args.recipient.as_deref().unwrap_or(&wallet);
// Validate recipient is a well-formed EVM address
if !recipient.starts_with("0x") || recipient.len() != 42
|| !recipient[2..].chars().all(|c| c.is_ascii_hexdigit())
{
anyhow::bail!("Invalid recipient address '{}': must be a 42-character hex address (0x...)", recipient);
}
let sym_in = if token_symbol(&origin_token, args.from_chain) != "UNKNOWN" {
token_symbol(&origin_token, args.from_chain).to_string()
} else { args.token.clone() };
let sym_out = if token_symbol(&dest_token, args.to_chain) != "UNKNOWN" {
token_symbol(&dest_token, args.to_chain).to_string()
} else { dest_token_input.to_string() };
let quote = get_quote(QuoteRequest {
user: wallet.clone(),
recipient: recipient.to_string(),
origin_chain_id: args.from_chain,
destination_chain_id: args.to_chain,
origin_currency: origin_token.clone(),
destination_currency: dest_token.clone(),
amount: amount_raw.to_string(),
trade_type: "EXACT_INPUT".to_string(),
}).await.map_err(|e| {
let msg = e.to_string();
// API returns 500 for unrecognised chain IDs — surface a helpful hint
if msg.contains("500") || msg.contains("validating") {
anyhow::anyhow!("{}\nTip: run `relay chains` to see all supported chain IDs.", e)
} else {
e
}
})?;
let request_id = quote.steps.first()
.and_then(|s| s.request_id.as_deref())
.unwrap_or("unknown")
.to_string();
let amount_out_fmt = quote.details.as_ref()
.and_then(|d| d.currency_out.as_ref())
.and_then(|c| c.amount_formatted.as_deref())
.unwrap_or(&format_value(amount_raw, decimals))
.to_string();
let amount_out_usd = quote.details.as_ref()
.and_then(|d| d.currency_out.as_ref())
.and_then(|c| c.amount_usd.as_deref())
.unwrap_or("unknown")
.to_string();
let time_secs = quote.details.as_ref()
.and_then(|d| d.time_estimate)
.unwrap_or(0);
let steps_summary: Vec<&str> = quote.steps.iter().map(|s| s.id.as_str()).collect();
let preview_fee_usd = quote.fees.as_ref()
.and_then(|f| f.pointer("/relayer/amountUsd"))
.and_then(|v| v.as_str())
.unwrap_or("unknown");
// Balance warning — fetch on-chain balance and warn if amount exceeds it.
// Only for native ETH (ERC-20 balance check would require a separate call per token).
// A failed RPC returns 0 so this never blocks the preview.
let balance_warning: Option<String> = if origin_token == NATIVE_ETH {
let balance = get_eth_balance(args.from_chain, &wallet).await;
if balance > 0 && amount_raw > balance {
let bal_eth = format_value(balance, 18);
Some(format!(
"Requested {} ETH exceeds wallet balance of {} ETH. \
The transaction will revert on-chain.",
args.amount, bal_eth
))
} else {
None
}
} else {
None
};
let mut preview = serde_json::json!({
"preview": true,
"action": "bridge",
"token_in": sym_in,
"token_out": sym_out,
"amount_in": args.amount,
"amount_out": amount_out_fmt,
"amount_out_usd": amount_out_usd,
"fee_usd": preview_fee_usd,
"from_chain": args.from_chain,
"to_chain": args.to_chain,
"recipient": recipient,
"wallet": wallet,
"estimated_time_secs": time_secs,
"steps": steps_summary,
"request_id": request_id,
});
if let Some(ref warn) = balance_warning {
preview["balance_warning"] = serde_json::json!(warn);
}
if !args.confirm && !args.dry_run {
println!("{}", serde_json::to_string_pretty(&preview)?);
eprintln!("\nAdd --confirm to broadcast this bridge transaction.");
eprintln!("Track status with: relay status --request-id {}", request_id);
return Ok(());
}
// Block --confirm execution if the balance check detected insufficient funds.
// A failed RPC (balance == 0) skips the check, so this never false-positives
// on an unreachable node.
if args.confirm {
if let Some(ref warn) = balance_warning {
anyhow::bail!("{} Refusing to broadcast.", warn);
}
}
// Execute each step in order
let mut tx_hashes: Vec<serde_json::Value> = Vec::new();
for step in "e.steps {
let step_id = step.id.as_str();
for item in &step.items {
let data = &item.data;
let to = &data.to;
let calldata = &data.data;
let value = data.value.as_deref().unwrap_or("0");
let step_chain = data.chain_id.unwrap_or(args.from_chain);
if step_id == "approve" {
eprintln!("[relay] Approving unlimited {} for bridge...", sym_in);
} else {
eprintln!("[relay] Sending {} {} from chain {} to chain {}...",
args.amount, sym_in, args.from_chain, args.to_chain);
}
let result = wallet_contract_call(
step_chain, to, calldata, value,
true, args.dry_run, Some(&wallet),
)?;
let tx_hash = extract_tx_hash(&result);
eprintln!("[relay] {} tx: {}", step_id, tx_hash);
let explorer = if !args.dry_run && !tx_hash.starts_with("0x000000000000") {
explorer_tx_url(step_chain, &tx_hash)
} else {
String::new()
};
let mut tx_entry = serde_json::json!({
"step": step_id,
"tx_hash": tx_hash,
"chain": step_chain,
});
if !explorer.is_empty() {
tx_entry["explorer"] = serde_json::json!(explorer);
}
tx_hashes.push(tx_entry);
// Wait between steps so approval confirms before deposit
if step_id == "approve" && !args.dry_run {
sleep(Duration::from_secs(5)).await;
}
}
}
let fee_usd = quote.fees.as_ref()
.and_then(|f| f.pointer("/relayer/amountUsd"))
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let mut out = serde_json::json!({
"ok": true,
"action": "bridge",
"token_in": sym_in,
"token_out": sym_out,
"amount_in": args.amount,
"amount_out": amount_out_fmt,
"fee_usd": fee_usd,
"from_chain": args.from_chain,
"to_chain": args.to_chain,
"wallet": wallet,
"recipient": recipient,
"request_id": request_id,
"txs": tx_hashes,
"track": format!("relay status --request-id {}", request_id),
});
if args.dry_run { out["dry_run"] = serde_json::json!(true); }
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
use clap::Args;
use crate::api::get_chains;
#[derive(Args)]
pub struct ChainsArgs {
/// Filter by chain name or ID (optional)
#[arg(long)]
pub filter: Option<String>,
}
pub async fn run(args: ChainsArgs) -> anyhow::Result<()> {
let mut chains = get_chains().await?;
// Filter out disabled chains
chains.retain(|c| c.disabled != Some(true));
// Apply optional name/id filter
if let Some(f) = &args.filter {
let f_lower = f.to_lowercase();
chains.retain(|c| {
c.name.to_lowercase().contains(&f_lower)
|| c.id.to_string() == f_lower
|| c.display_name.as_deref().unwrap_or("").to_lowercase().contains(&f_lower)
});
}
let out: Vec<serde_json::Value> = chains.iter().map(|c| {
serde_json::json!({
"chain_id": c.id,
"name": c.display_name.as_deref().unwrap_or(&c.name),
"slug": c.name,
"native_token": c.currency.as_ref().map(|cu| cu.symbol.as_str()).unwrap_or("ETH"),
"explorer": c.explorer_url.as_deref().unwrap_or(""),
})
}).collect();
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
pub mod bridge;
pub mod chains;
pub mod quote;
pub mod status;
use clap::Args;
use crate::api::{get_quote, resolve_token, token_symbol, QuoteRequest, NATIVE_ETH};
use crate::onchainos::resolve_wallet;
#[derive(Args)]
pub struct QuoteArgs {
/// Source chain ID (e.g. 1 for Ethereum, 42161 for Arbitrum)
#[arg(long)]
pub from_chain: u64,
/// Destination chain ID
#[arg(long)]
pub to_chain: u64,
/// Token to send (symbol or address, e.g. ETH, USDC, 0x...)
#[arg(long, default_value = "ETH")]
pub token: String,
/// Amount to send in human-readable form (e.g. 0.01 for 0.01 ETH)
#[arg(long)]
pub amount: String,
/// Destination token (defaults to same as --token)
#[arg(long)]
pub to_token: Option<String>,
}
pub async fn run(args: QuoteArgs) -> anyhow::Result<()> {
let origin_token = resolve_token(&args.token, args.from_chain);
let dest_token_input = args.to_token.as_deref().unwrap_or(&args.token);
let dest_token = resolve_token(dest_token_input, args.to_chain);
// Parse amount: need raw amount in smallest unit
let amount_raw = parse_human_amount_eth(&args.amount, &origin_token, args.from_chain)?;
// Quote only — wallet not required; fall back to zero address if none found
let wallet = resolve_wallet(args.from_chain)
.unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string());
let quote = get_quote(QuoteRequest {
user: wallet.clone(),
recipient: wallet.clone(),
origin_chain_id: args.from_chain,
destination_chain_id: args.to_chain,
origin_currency: origin_token.clone(),
destination_currency: dest_token.clone(),
amount: amount_raw.to_string(),
trade_type: "EXACT_INPUT".to_string(),
}).await?;
let request_id = quote.steps.first()
.and_then(|s| s.request_id.as_deref())
.unwrap_or("unknown");
let sym_in = if token_symbol(&origin_token, args.from_chain) != "UNKNOWN" {
token_symbol(&origin_token, args.from_chain).to_string()
} else { args.token.clone() };
let sym_out = if token_symbol(&dest_token, args.to_chain) != "UNKNOWN" {
token_symbol(&dest_token, args.to_chain).to_string()
} else { dest_token_input.to_string() };
let amount_out_fmt = quote.details.as_ref()
.and_then(|d| d.currency_out.as_ref())
.and_then(|c| c.amount_formatted.as_deref())
.unwrap_or("unknown");
let amount_out_usd = quote.details.as_ref()
.and_then(|d| d.currency_out.as_ref())
.and_then(|c| c.amount_usd.as_deref())
.unwrap_or("unknown");
let time_secs = quote.details.as_ref()
.and_then(|d| d.time_estimate)
.unwrap_or(0);
let fee_usd = quote.fees.as_ref()
.and_then(|f| f.pointer("/relayer/amountUsd"))
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let steps_summary: Vec<&str> = quote.steps.iter().map(|s| s.id.as_str()).collect();
let out = serde_json::json!({
"token_in": sym_in,
"token_out": sym_out,
"amount_in": args.amount,
"amount_out": amount_out_fmt,
"amount_out_usd": amount_out_usd,
"fee_usd": fee_usd,
"from_chain": args.from_chain,
"to_chain": args.to_chain,
"estimated_time_secs": time_secs,
"steps": steps_summary,
"request_id": request_id,
});
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
/// Parse a human-readable amount into raw units (wei for ETH, 6 decimals for USDC/USDT, 18 for DAI).
pub fn parse_human_amount_eth(s: &str, token_addr: &str, chain_id: u64) -> anyhow::Result<u128> {
let decimals: u8 = if token_addr == NATIVE_ETH {
18
} else {
match token_symbol(token_addr, chain_id) {
"USDC" | "USDT" => 6,
"DAI" => 18,
_ => 18,
}
};
crate::commands::bridge::parse_amount(s, decimals)
}
use clap::Args;
use crate::api::get_status;
#[derive(Args)]
pub struct StatusArgs {
/// Request ID from a previous bridge command
#[arg(long)]
pub request_id: String,
}
pub async fn run(args: StatusArgs) -> anyhow::Result<()> {
let status = get_status(&args.request_id).await?;
let origin_tx = status.in_tx_hashes.as_ref()
.and_then(|txs| txs.first())
.map(|s| s.as_str())
.unwrap_or("pending");
let dest_tx = status.tx_hashes.as_ref()
.and_then(|txs| txs.first())
.map(|s| s.as_str())
.unwrap_or("pending");
let out = serde_json::json!({
"status": status.status,
"request_id": args.request_id,
"origin_tx": origin_tx,
"dest_tx": dest_tx,
"error": status.error.as_deref().unwrap_or(""),
});
println!("{}", serde_json::to_string_pretty(&out)?);
// "unknown" means not yet indexed — this is a normal transient state,
// not an error. Print a hint on stderr but exit 0 so callers can pipe the JSON.
if status.status == "unknown" {
eprintln!("[relay] Request not yet indexed. Wait a few seconds and try again.");
}
Ok(())
}
mod api;
mod commands;
mod onchainos;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "relay-plugin", version, about = "Fast cross-chain transfers via Relay Protocol")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// List supported chains
Chains(commands::chains::ChainsArgs),
/// Get a cross-chain transfer quote (read-only)
Quote(commands::quote::QuoteArgs),
/// Execute a cross-chain bridge transfer
Bridge(commands::bridge::BridgeArgs),
/// Check the status of a bridge transfer by request ID
Status(commands::status::StatusArgs),
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let result = match cli.command {
Commands::Chains(args) => commands::chains::run(args).await,
Commands::Quote(args) => commands::quote::run(args).await,
Commands::Bridge(args) => commands::bridge::run(args).await,
Commands::Status(args) => commands::status::run(args).await,
};
if let Err(e) = result {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
use anyhow::Context;
use std::process::Command;
pub fn resolve_wallet(chain_id: u64) -> anyhow::Result<String> {
let output = Command::new("onchainos")
.args(["wallet", "addresses"])
.output()
.context("onchainos not found — install from https://docs.onchainos.com")?;
let stdout = String::from_utf8_lossy(&output.stdout);
let v: serde_json::Value = serde_json::from_str(stdout.trim())
.context("Could not parse onchainos wallet output")?;
let chain_str = chain_id.to_string();
if let Some(evm_list) = v["data"]["evm"].as_array() {
for entry in evm_list {
if entry["chainIndex"].as_str() == Some(&chain_str) {
if let Some(addr) = entry["address"].as_str() {
return Ok(addr.to_string());
}
}
}
}
anyhow::bail!("No EVM wallet found for chain {}. Run: onchainos wallet login", chain_id)
}
pub fn wallet_contract_call(
chain_id: u64,
to: &str,
input_data: &str,
value: &str, // hex (0x...) or decimal string — will be converted to decimal wei
_wait: bool,
dry_run: bool,
from: Option<&str>,
) -> anyhow::Result<String> {
if dry_run {
// Return a stub result without calling onchainos
return Ok(serde_json::json!({
"txHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"dry_run": true
}).to_string());
}
// Convert value from hex or decimal to decimal integer for --amt
let amt = if value.starts_with("0x") || value.starts_with("0X") {
u128::from_str_radix(value.trim_start_matches("0x").trim_start_matches("0X"), 16)
.unwrap_or(0)
.to_string()
} else if value.is_empty() {
"0".to_string()
} else {
value.to_string()
};
let mut args = vec![
"wallet".to_string(),
"contract-call".to_string(),
"--chain".to_string(), chain_id.to_string(),
"--to".to_string(), to.to_string(),
"--input-data".to_string(), input_data.to_string(),
"--amt".to_string(), amt,
"--biz-type".to_string(), "dapp".to_string(),
"--strategy".to_string(), "relay-plugin".to_string(),
];
if let Some(f) = from { args.extend(["--from".to_string(), f.to_string()]); }
let output = Command::new("onchainos").args(&args).output()
.context("onchainos not found")?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
if stdout.trim().is_empty() {
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
anyhow::bail!("onchainos error: {}", stderr.trim());
}
// Surface onchainos errors rather than returning zero tx_hash silently
if let Ok(v) = serde_json::from_str::<serde_json::Value>(stdout.trim()) {
if v.get("ok").and_then(|o| o.as_bool()) == Some(false) {
let msg = v.get("error").and_then(|e| e.as_str()).unwrap_or("unknown error");
anyhow::bail!("onchainos error: {}", msg);
}
}
Ok(stdout)
}
pub fn extract_tx_hash(output: &str) -> String {
serde_json::from_str::<serde_json::Value>(output.trim())
.ok()
.and_then(|v| {
// onchainos returns {"ok":true,"data":{"txHash":"0x..."}}
v.pointer("/data/txHash")
.or_else(|| v.pointer("/data/tx_hash"))
.or_else(|| v.get("txHash"))
.or_else(|| v.get("tx_hash"))
.and_then(|h| h.as_str())
.map(|s| s.to_string())
})
.unwrap_or_else(|| "0x0000000000000000000000000000000000000000000000000000000000000000".to_string())
}
Relay — Plugin Summary
Version: 0.1.1
Overview
Relay Protocol is an intent-based cross-chain bridge that delivers funds across chains in seconds. Users specify the source chain, destination chain, token, and amount — Relay handles the rest via an off-chain solver network.
Core operations:
- Bridge ETH and ERC-20 tokens (USDC, USDT, DAI) across 70+ chains
- Get quotes with real-time fee and output estimates
- Track transfer status by request ID
Tags: defi bridge cross-chain multi-chain
Prerequisites
- No IP/region restrictions
- Supported chains: Ethereum (1), Arbitrum (42161), Base (8453), Optimism (10), Polygon (137), and 65+ more — run
relay chainsfor the full list - Supported tokens: ETH (native), USDC, USDT, DAI; any ERC-20 by address
- onchainos CLI installed and authenticated with an active EVM wallet on the source chain
- Sufficient balance on the source chain for the bridge amount plus gas
Quick Start
1. Check chains: relay chains — view all 70+ supported chains 2. Get a quote: relay quote --from-chain 1 --to-chain 42161 --token ETH --amount 0.01 3. Preview bridge: relay bridge --from-chain 1 --to-chain 42161 --token ETH --amount 0.01 4. Execute: Add --confirm to the bridge command to send the transaction 5. Track: relay status --request-id <id> — check if funds arrived