diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcea29ec..d96e138b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,6 @@ on: - 'release/**' - 'master' pull_request: - branches: - - 'master' - - '**' - types: [opened, synchronize, reopened] jobs: fmt: @@ -20,10 +16,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - # Toolchain file bug workaround: https://github.com/dtolnay/rust-toolchain/issues/153 - run: rustup component add rustfmt - - name: Run Fmt Check - run: cargo fmt --all -- --check + - run: cargo fmt --all -- --check clippy: name: Clippy @@ -33,226 +27,30 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: rustup component add clippy - - name: Run clippy - run: cargo clippy -- -D warnings -A clippy::uninlined_format_args - - build: - if: | - (github.event_name == 'push' || github.event_name == 'pull_request') && - github.actor != 'github-actions[bot]' && - ( - !(github.event_name == 'pull_request' && - startsWith(github.event.pull_request.head.ref, 'release/') && - github.event.pull_request.base.ref == 'master') || - (github.event_name == 'pull_request' && - (contains(github.event.pull_request.head.ref, '/merge') || - startsWith(github.event.pull_request.head.ref, 'merge'))) - ) && - !(github.ref == 'refs/heads/master' && - github.event_name == 'push' && - (contains(github.event.head_commit.message, 'Release v') || - contains(github.event.head_commit.message, 'release/'))) - name: ${{ matrix.target }} (${{ matrix.runner }}) - runs-on: ${{ matrix.runner }} - timeout-minutes: 240 - strategy: - fail-fast: false - matrix: - include: - - runner: ubuntu-22.04 - target: x86_64-unknown-linux-gnu - platform: linux - arch: amd64 - - runner: macos-15-intel - target: x86_64-apple-darwin - platform: darwin - arch: amd64 - - runner: macos-latest - target: aarch64-apple-darwin - platform: darwin - arch: arm64 - - runner: windows-latest - target: x86_64-pc-windows-msvc - platform: win32 - arch: amd64 - - env: - BUILD_TYPE: release + - run: cargo clippy -- -D warnings -A clippy::uninlined_format_args + test: + name: Test + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - with: - ref: ${{ github.event_name == 'pull_request' && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.ref_name }} - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: - key: ${{ matrix.target }} cache-on-failure: true - - - name: Apple M1 setup - if: matrix.target == 'aarch64-apple-darwin' - run: | - echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV - echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV - - - name: Linux ARM setup - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get update -y - sudo apt-get install -y gcc-aarch64-linux-gnu - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - - - name: Install MSVC target - if: matrix.target == 'x86_64-pc-windows-msvc' - run: rustup target add x86_64-pc-windows-msvc - - - name: Install OpenSSL development libraries - if: matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'x86_64-unknown-linux-gnu' + - name: Install dependencies run: | sudo apt-get update -y sudo apt-get install -y libssl-dev pkg-config - - - name: Setup cross-compilation for pkg-config - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV - echo "PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig" >> $GITHUB_ENV - echo "PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig" >> $GITHUB_ENV - - - name: Extract version name - id: extract_version - shell: bash - run: echo "VERSION_NAME=${GITHUB_REF#refs/heads/release/}" >> $GITHUB_ENV - - - name: Install vcpkg on Windows - if: matrix.target == 'x86_64-pc-windows-msvc' - shell: pwsh - run: | - git clone https://github.com/Microsoft/vcpkg.git C:\vcpkg - C:\vcpkg\bootstrap-vcpkg.bat - C:\vcpkg\vcpkg integrate install - echo "VCPKG_ROOT=C:\vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "C:\vcpkg" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Install CMake on Windows - if: matrix.target == 'x86_64-pc-windows-msvc' - shell: pwsh - run: | - choco install cmake --version=3.20.0 --installargs 'ADD_CMAKE_TO_PATH=System' - refreshenv - cmake --version - echo "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Install dependencies with vcpkg on Windows - if: matrix.target == 'x86_64-pc-windows-msvc' - shell: pwsh - run: | - C:\vcpkg\vcpkg install openssl:x64-windows-static-md zlib:x64-windows-static-md - - - name: Build binaries - working-directory: crates/cli - env: - CMAKE_TOOLCHAIN_FILE: C:/vcpkg/scripts/buildsystems/vcpkg.cmake - shell: bash - run: | - set -eo pipefail - target="${{ matrix.target }}" - flags=() - - if [[ "$target" != *msvc* && "$target" != "aarch64-unknown-linux-gnu" ]]; then - flags+=(--features jemalloc) - fi - - [[ "$target" == *windows* ]] && exe=".exe" - - if [[ "$target" == *windows* ]]; then - export PATH="$PATH:/c/vcpkg" - echo "CMAKE_TOOLCHAIN_FILE: $CMAKE_TOOLCHAIN_FILE" - ls -l "$CMAKE_TOOLCHAIN_FILE" || echo "Toolchain file not found!" - fi - - if [[ "${{ env.BUILD_TYPE }}" == "release" ]]; then - RUST_BACKTRACE=1 CMAKE_TOOLCHAIN_FILE="$CMAKE_TOOLCHAIN_FILE" cargo build --release --target "$target" "${flags[@]}" -vv - else - RUST_BACKTRACE=1 CMAKE_TOOLCHAIN_FILE="$CMAKE_TOOLCHAIN_FILE" cargo build --target "$target" "${flags[@]}" -vv - fi - - - name: Smoke Test - shell: bash - run: | - set -eo pipefail - target="${{ matrix.target }}" - build_type="${{ env.BUILD_TYPE }}" - binary_path="${{ github.workspace }}/target/$target/$build_type/rrelayer_cli" - - if [[ "$target" == *windows* ]]; then - binary_path+=".exe" - fi - - echo "Running smoke test for $binary_path" - "$binary_path" --version - "$binary_path" help - - - name: Archive binaries - id: artifacts - if: startsWith(github.ref, 'refs/heads/release/') - env: - PLATFORM_NAME: ${{ matrix.platform }} - TARGET: ${{ matrix.target }} - ARCH: ${{ matrix.arch }} - VERSION_NAME: ${{ env.VERSION_NAME }} - shell: bash - run: | - set -eo pipefail - BUILD_DIR="${{ github.workspace }}/target/${TARGET}/${{ env.BUILD_TYPE }}" - CLI_BINARY_NAME="rrelayer_cli" - [[ "$PLATFORM_NAME" == "win32" ]] && CLI_BINARY_NAME="rrelayer_cli.exe" - - # Create a temporary staging directory for creating the archive - STAGING_DIR="staging" - mkdir -p "$STAGING_DIR" - - # Copy binaries to the staging directory - echo "Copying $BUILD_DIR/$CLI_BINARY_NAME to $STAGING_DIR/" - cp "$BUILD_DIR/$CLI_BINARY_NAME" "$STAGING_DIR/" - - # Create the final archive - if [ "$PLATFORM_NAME" == "linux" ] || [ "$PLATFORM_NAME" == "darwin" ]; then - FILE_NAME="rrelayer_${PLATFORM_NAME}-${ARCH}.tar.gz" - tar -czvf "$FILE_NAME" -C "$STAGING_DIR" . - else - FILE_NAME="rrelayer_${PLATFORM_NAME}-${ARCH}.zip" - (cd "$STAGING_DIR" && 7z a -tzip "${{ github.workspace }}/$FILE_NAME" .) - fi - - echo "Created archive: $FILE_NAME" - echo "file_name=$FILE_NAME" >> $GITHUB_OUTPUT - - name: Run tests - shell: bash - run: | - set -eo pipefail - target="${{ matrix.target }}" - - cargo test --exclude rust-sdk-playground --workspace --release --target "$target" - - - name: Upload artifact - if: startsWith(github.ref, 'refs/heads/release/') - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.platform }}-${{ matrix.arch }} - path: ${{ steps.artifacts.outputs.file_name }} + run: cargo test --exclude rust-sdk-playground --workspace create_pr: name: Create Release PR runs-on: ubuntu-22.04 - needs: build + needs: test if: | - github.actor != 'github-actions[bot]' && + github.actor != 'github-actions[bot]' && startsWith(github.ref, 'refs/heads/release/') steps: - uses: actions/checkout@v4 @@ -261,172 +59,17 @@ jobs: fetch-depth: 0 - name: Extract version from branch name - shell: bash run: | VERSION=${GITHUB_REF#refs/heads/release/} echo "VERSION_NAME=$VERSION" >> $GITHUB_ENV - name: Update Cargo.toml versions - shell: bash run: | sed -i 's/^version = ".*"/version = "${{ env.VERSION_NAME }}"/' crates/cli/Cargo.toml sed -i 's/^version = ".*"/version = "${{ env.VERSION_NAME }}"/' crates/core/Cargo.toml sed -i 's/^version = ".*"/version = "${{ env.VERSION_NAME }}"/' crates/sdk/Cargo.toml sed -i 's/^version = ".*"/version = "${{ env.VERSION_NAME }}"/' Cargo.toml - # Temporarily commented out changelog update for release process - # - name: Update Changelog - # shell: bash - # run: | - # CHANGELOG_FILE="documentation/rrelayer/docs/pages/changelog.mdx" - # DAY=$(date '+%d' | sed 's/^0*//') - # MONTH=$(date '+%B') - # YEAR=$(date '+%Y') - # - # # Add ordinal suffix - # case $DAY in - # 1|21|31) SUFFIX="st";; - # 2|22) SUFFIX="nd";; - # 3|23) SUFFIX="rd";; - # *) SUFFIX="th";; - # esac - # - # DATE="$DAY$SUFFIX $MONTH $YEAR" - # - # echo "Updating changelog for version ${{ env.VERSION_NAME }}" - # - # # Create changelog if it doesn't exist - # if [[ ! -f "$CHANGELOG_FILE" ]]; then - # cat > "$CHANGELOG_FILE" << EOF - # # Changelog - # - # ## Changes Not Deployed - # ------------------------------------------------- - # - # ### Features - # ------------------------------------------------- - # - # ### Bug fixes - # ------------------------------------------------- - # - # ### Breaking changes - # ------------------------------------------------- - # - # ## Releases - # ------------------------------------------------- - # - # all release branches are deployed through \`release/VERSION_NUMBER\` branches - # - # EOF - # fi - # - # # Create a temporary file to work with - # cp "$CHANGELOG_FILE" changelog_temp.md - # - # # Extract just the bug fixes line directly from Changes Not Deployed section - # BUG_FIXES=$(sed -n '/^## Changes Not Deployed/,/^## Releases/p' changelog_temp.md | grep "^- fix:" | head -5) - # - # # Use the simple extraction for now - # FEATURES="" - # BUG_FIXES="$BUG_FIXES" - # BREAKING_CHANGES="" - # - # # Save the extracted content for PR creation - # { - # echo "### Changes in this release:" - # echo "-------------------------------------------------" - # echo "### Features" - # echo "-------------------------------------------------" - # if [[ -n "$FEATURES" ]]; then - # echo "$FEATURES" - # fi - # echo "" - # echo "### Bug fixes" - # echo "-------------------------------------------------" - # if [[ -n "$BUG_FIXES" ]]; then - # echo "$BUG_FIXES" - # fi - # echo "" - # echo "### Breaking changes" - # echo "-------------------------------------------------" - # if [[ -n "$BREAKING_CHANGES" ]]; then - # echo "$BREAKING_CHANGES" - # fi - # } > pr_body_content.txt - # - # # Save to environment variable - # echo "PR_BODY_CONTENT<> $GITHUB_ENV - # cat pr_body_content.txt >> $GITHUB_ENV - # echo "EOF" >> $GITHUB_ENV - # - # # Create new release entry - # cat > new_release.txt << EOF - # # ${{ env.VERSION_NAME }}-beta - $DATE - # - # github branch - https://github.com/joshstevens19/rrelayer/tree/release/${{ env.VERSION_NAME }} - # - # - linux binary - https://github.com/joshstevens19/rrelayer/releases/download/v${{ env.VERSION_NAME }}/rrelayer_linux-amd64.tar.gz - # - mac apple silicon binary - https://github.com/joshstevens19/rrelayer/releases/download/v${{ env.VERSION_NAME }}/rrelayer_darwin-arm64.tar.gz - # - mac apple intel binary - https://github.com/joshstevens19/rrelayer/releases/download/v${{ env.VERSION_NAME }}/rrelayer_darwin-amd64.tar.gz - # - windows binary - https://github.com/joshstevens19/rrelayer/releases/download/v${{ env.VERSION_NAME }}/rrelayer_win32-amd64.zip - # EOF - # - # # Add sections if they have content - # if [[ -n "$FEATURES" && "$FEATURES" =~ [^[:space:]] ]]; then - # echo "" >> new_release.txt - # echo "### Features" >> new_release.txt - # echo "-------------------------------------------------" >> new_release.txt - # echo "$FEATURES" >> new_release.txt - # fi - # - # if [[ -n "$BUG_FIXES" && "$BUG_FIXES" =~ [^[:space:]] ]]; then - # echo "" >> new_release.txt - # echo "### Bug fixes" >> new_release.txt - # echo "-------------------------------------------------" >> new_release.txt - # echo "$BUG_FIXES" >> new_release.txt - # fi - # - # if [[ -n "$BREAKING_CHANGES" && "$BREAKING_CHANGES" =~ [^[:space:]] ]]; then - # echo "" >> new_release.txt - # echo "### Breaking changes" >> new_release.txt - # echo "-------------------------------------------------" >> new_release.txt - # echo "$BREAKING_CHANGES" >> new_release.txt - # fi - # - # # Get existing releases (everything after "## Releases") - # EXISTING_RELEASES=$(awk '/^## Releases/,0' changelog_temp.md | tail -n +5) - # - # # Create new changelog - # cat > "$CHANGELOG_FILE" << EOF - # # Changelog - # - # ## Changes Not Deployed - # ------------------------------------------------- - # - # ### Features - # ------------------------------------------------- - # - # ### Bug fixes - # ------------------------------------------------- - # - # ### Breaking changes - # ------------------------------------------------- - # - # ## Releases - # ------------------------------------------------- - # - # all release branches are deployed through \`release/VERSION_NUMBER\` branches - # - # $(cat new_release.txt) - # - # $EXISTING_RELEASES - # EOF - # - # # Clean up temporary files - # rm -f changelog_temp.md new_release.txt pr_body_content.txt - # - # echo "Changelog updated successfully" - - name: Commit changes run: | git config --local user.email "action@github.com" @@ -443,10 +86,8 @@ jobs: PR_EXISTS=$(gh pr list --base master --head release/${{ env.VERSION_NAME }} --json number --jq length) if [ "$PR_EXISTS" -gt 0 ]; then echo "pr_exists=true" >> $GITHUB_OUTPUT - echo "PR already exists, skipping creation" else echo "pr_exists=false" >> $GITHUB_OUTPUT - echo "No PR exists, will create one" fi - name: Create Pull Request @@ -459,334 +100,43 @@ jobs: --body "## Release v${{ env.VERSION_NAME }} This PR contains: - - ✅ Version bump to ${{ env.VERSION_NAME }} - - ✅ Changelog updated with release notes - - ✅ Ready for release + - Version bump to ${{ env.VERSION_NAME }} - **Merging this PR will automatically create a GitHub Release with binaries.** - - ${{ env.PR_BODY_CONTENT }}" \ + **Merging this PR will automatically create a GitHub Release and build Docker images.**" \ --base master \ --head release/${{ env.VERSION_NAME }} - release_build: - name: Build Release Binaries - runs-on: ${{ matrix.runner }} - if: | - github.actor != 'github-actions[bot]' && - github.ref == 'refs/heads/master' && - github.event_name == 'push' - timeout-minutes: 240 - strategy: - fail-fast: false - matrix: - include: - - runner: ubuntu-22.04 - target: x86_64-unknown-linux-gnu - platform: linux - arch: amd64 - - runner: macos-15-intel - target: x86_64-apple-darwin - platform: darwin - arch: amd64 - - runner: macos-latest - target: aarch64-apple-darwin - platform: darwin - arch: arm64 - - runner: windows-latest - target: x86_64-pc-windows-msvc - platform: win32 - arch: amd64 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if this is a release commit and extract version - id: check_release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - echo "=== DEBUG: Checking recent commits ===" - git log --oneline -10 --pretty=format:"%H %s" - echo "" - echo "=== DEBUG: Checking for release pattern ===" - - # Check the most recent commit (HEAD) for release pattern - RECENT_COMMIT_MSG=$(git log --oneline -1 --pretty=format:"%s") - echo "Most recent commit: '$RECENT_COMMIT_MSG'" - - # Try to extract version from Release v pattern (including PR number) - VERSION_FROM_RELEASE=$(echo "$RECENT_COMMIT_MSG" | grep -o 'Release v[0-9]*\.[0-9]*\.[0-9]*' | sed 's/Release v//' || echo "") - echo "VERSION_FROM_RELEASE: '$VERSION_FROM_RELEASE'" - - if [[ -n "$VERSION_FROM_RELEASE" ]]; then - echo "VERSION_NAME=$VERSION_FROM_RELEASE" >> $GITHUB_ENV - echo "is_release=true" >> $GITHUB_OUTPUT - echo "Found release from commit title: Release v$VERSION_FROM_RELEASE" - else - echo "Not a release commit" - echo "is_release=false" >> $GITHUB_OUTPUT - fi - - - name: Skip if not a release - if: steps.check_release.outputs.is_release != 'true' - run: | - echo "Skipping release build - not a release commit" - exit 0 - - - name: Checkout release branch - if: steps.check_release.outputs.is_release == 'true' - uses: actions/checkout@v4 - with: - ref: release/${{ env.VERSION_NAME }} - fetch-depth: 0 - - - uses: dtolnay/rust-toolchain@stable - if: steps.check_release.outputs.is_release == 'true' - with: - targets: ${{ matrix.target }} - - - uses: Swatinem/rust-cache@v2 - if: steps.check_release.outputs.is_release == 'true' - with: - key: ${{ matrix.target }} - cache-on-failure: true - - - name: Apple M1 setup - if: steps.check_release.outputs.is_release == 'true' && matrix.target == 'aarch64-apple-darwin' - run: | - echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV - echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV - - - name: Install MSVC target - if: steps.check_release.outputs.is_release == 'true' && matrix.target == 'x86_64-pc-windows-msvc' - run: rustup target add x86_64-pc-windows-msvc - - - name: Install OpenSSL development libraries - if: steps.check_release.outputs.is_release == 'true' && matrix.target == 'x86_64-unknown-linux-gnu' - run: | - sudo apt-get update -y - sudo apt-get install -y libssl-dev pkg-config - - - name: Install vcpkg on Windows - if: steps.check_release.outputs.is_release == 'true' && matrix.target == 'x86_64-pc-windows-msvc' - shell: pwsh - run: | - git clone https://github.com/Microsoft/vcpkg.git C:\vcpkg - C:\vcpkg\bootstrap-vcpkg.bat - C:\vcpkg\vcpkg integrate install - echo "VCPKG_ROOT=C:\vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "C:\vcpkg" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Install CMake on Windows - if: steps.check_release.outputs.is_release == 'true' && matrix.target == 'x86_64-pc-windows-msvc' - shell: pwsh - run: | - choco install cmake --version=3.20.0 --installargs 'ADD_CMAKE_TO_PATH=System' - refreshenv - cmake --version - echo "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Install dependencies with vcpkg on Windows - if: steps.check_release.outputs.is_release == 'true' && matrix.target == 'x86_64-pc-windows-msvc' - shell: pwsh - run: | - C:\vcpkg\vcpkg install openssl:x64-windows-static-md zlib:x64-windows-static-md - - - name: Build binaries - if: steps.check_release.outputs.is_release == 'true' - working-directory: crates/cli - env: - CMAKE_TOOLCHAIN_FILE: C:/vcpkg/scripts/buildsystems/vcpkg.cmake - shell: bash - run: | - set -eo pipefail - target="${{ matrix.target }}" - - if [[ "$target" != *msvc* && "$target" != "aarch64-unknown-linux-gnu" ]]; then - flags+=(--features jemalloc) - fi - - if [[ "$target" == *windows* ]]; then - export PATH="$PATH:/c/vcpkg" - echo "CMAKE_TOOLCHAIN_FILE: $CMAKE_TOOLCHAIN_FILE" - ls -l "$CMAKE_TOOLCHAIN_FILE" || echo "Toolchain file not found!" - fi - - RUST_BACKTRACE=1 CMAKE_TOOLCHAIN_FILE="$CMAKE_TOOLCHAIN_FILE" cargo build --release --target "$target" -vv - - - name: Smoke Test - if: steps.check_release.outputs.is_release == 'true' - shell: bash - run: | - set -eo pipefail - target="${{ matrix.target }}" - binary_path="${{ github.workspace }}/target/$target/release/rrelayer_cli" - - if [[ "$target" == *windows* ]]; then - binary_path+=".exe" - fi - - echo "Running smoke test for $binary_path" - "$binary_path" --version - "$binary_path" help - - - name: Archive binaries - if: steps.check_release.outputs.is_release == 'true' - id: artifacts - env: - PLATFORM_NAME: ${{ matrix.platform }} - TARGET: ${{ matrix.target }} - ARCH: ${{ matrix.arch }} - VERSION_NAME: ${{ env.VERSION_NAME }} - shell: bash - run: | - set -eo pipefail - BUILD_DIR="${{ github.workspace }}/target/${TARGET}/release" - CLI_BINARY_NAME="rrelayer_cli" - [[ "$PLATFORM_NAME" == "win32" ]] && CLI_BINARY_NAME="rrelayer_cli.exe" - - # Create a temporary staging directory for creating the archive - STAGING_DIR="staging" - mkdir -p "$STAGING_DIR" - - # Copy binaries to the staging directory - echo "Copying $BUILD_DIR/$CLI_BINARY_NAME to $STAGING_DIR/" - cp "$BUILD_DIR/$CLI_BINARY_NAME" "$STAGING_DIR/" - - # Create the final archive - if [ "$PLATFORM_NAME" == "linux" ] || [ "$PLATFORM_NAME" == "darwin" ]; then - FILE_NAME="rrelayer_${PLATFORM_NAME}-${ARCH}.tar.gz" - tar -czvf "$FILE_NAME" -C "$STAGING_DIR" . - else - FILE_NAME="rrelayer_${PLATFORM_NAME}-${ARCH}.zip" - (cd "$STAGING_DIR" && 7z a -tzip "${{ github.workspace }}/$FILE_NAME" .) - fi - - echo "Created archive: $FILE_NAME" - echo "file_name=$FILE_NAME" >> $GITHUB_OUTPUT - - - name: Upload artifact - if: steps.check_release.outputs.is_release == 'true' - uses: actions/upload-artifact@v4 - with: - name: release-${{ matrix.platform }}-${{ matrix.arch }} - path: ${{ steps.artifacts.outputs.file_name }} - release: name: Create GitHub Release runs-on: ubuntu-22.04 - needs: release_build + needs: [fmt, clippy, test] if: | - github.actor != 'github-actions[bot]' && - github.ref == 'refs/heads/master' && + github.actor != 'github-actions[bot]' && + github.ref == 'refs/heads/master' && github.event_name == 'push' - outputs: - version: ${{ steps.check_release.outputs.version }} - is_release: ${{ steps.check_release.outputs.is_release }} steps: - - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Check if this is a release commit and extract version + - name: Check if this is a release commit id: check_release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash run: | - echo "=== DEBUG: Checking recent commits ===" - git log --oneline -10 --pretty=format:"%H %s" - echo "" - echo "=== DEBUG: Checking for release pattern ===" - - # Check the most recent commit (HEAD) for release pattern - RECENT_COMMIT_MSG=$(git log --oneline -1 --pretty=format:"%s") - echo "Most recent commit: '$RECENT_COMMIT_MSG'" - - # Try to extract version from Release v pattern (including PR number) - VERSION_FROM_RELEASE=$(echo "$RECENT_COMMIT_MSG" | grep -o 'Release v[0-9]*\.[0-9]*\.[0-9]*' | sed 's/Release v//' || echo "") - echo "VERSION_FROM_RELEASE: '$VERSION_FROM_RELEASE'" - - if [[ -n "$VERSION_FROM_RELEASE" ]]; then - echo "VERSION_NAME=$VERSION_FROM_RELEASE" >> $GITHUB_ENV + COMMIT_MSG=$(git log --oneline -1 --pretty=format:"%s") + VERSION=$(echo "$COMMIT_MSG" | grep -o 'Release v[0-9]*\.[0-9]*\.[0-9]*' | sed 's/Release v//' || echo "") + if [[ -n "$VERSION" ]]; then + echo "version=$VERSION" >> $GITHUB_OUTPUT echo "is_release=true" >> $GITHUB_OUTPUT - echo "version=$VERSION_FROM_RELEASE" >> $GITHUB_OUTPUT - echo "Found release from commit title: Release v$VERSION_FROM_RELEASE" else - echo "Not a release commit" echo "is_release=false" >> $GITHUB_OUTPUT - echo "version=" >> $GITHUB_OUTPUT fi - - name: Skip if not a release - if: steps.check_release.outputs.is_release != 'true' - run: | - echo "Skipping release creation - not a release commit" - exit 0 - - - name: Download release artifacts - if: steps.check_release.outputs.is_release == 'true' - uses: actions/download-artifact@v4 - with: - path: ./release-artifacts - - - name: Display structure of downloaded files - if: steps.check_release.outputs.is_release == 'true' - run: ls -la ./release-artifacts/ - - name: Create GitHub Release - if: steps.check_release.outputs.is_release == 'true' - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ env.VERSION_NAME }} - release_name: Release v${{ env.VERSION_NAME }} - draft: false - prerelease: false - body: | - Release v${{ env.VERSION_NAME }} - - ## Installation - ```bash - # Latest version - curl -sSfL https://docs.rrelayer.com/install.sh | bash - - # Specific version - curl -sSfL https://docs.rrelayer.com/install.sh | bash -s -- --version ${{ env.VERSION_NAME }} - ``` - continue-on-error: true - - - name: Upload Release Assets if: steps.check_release.outputs.is_release == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - for platform_dir in ./release-artifacts/*/; do - for file in "$platform_dir"*; do - if [[ -f "$file" ]]; then - filename=$(basename "$file") - echo "Uploading $filename" - gh release upload v${{ env.VERSION_NAME }} "$file" --clobber - fi - done - done - - docker: - name: Build and Publish Docker Image - needs: release - if: | - github.actor != 'github-actions[bot]' && - github.ref == 'refs/heads/master' && - github.event_name == 'push' - uses: ./.github/workflows/docker.yml - with: - version: ${{ needs.release.outputs.version }} - secrets: inherit + VERSION="${{ steps.check_release.outputs.version }}" + gh release create "v${VERSION}" \ + --title "Release v${VERSION}" \ + --generate-notes diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9ee58659..ba379f5e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,13 +4,7 @@ on: workflow_dispatch: inputs: version: - description: 'Release version (e.g., 1.2.3) - leave empty for master build' - required: false - type: string - workflow_call: - inputs: - version: - description: 'Release version (e.g., 1.2.3)' + description: 'Release version (e.g., 1.2.3) - leave empty for edge build' required: false type: string release: @@ -28,19 +22,13 @@ jobs: contents: read packages: write steps: - - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Create docker-binary directory - run: mkdir -p docker-binary - - - name: Set up Rust toolchain - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@v2 with: key: x86_64-unknown-linux-gnu-docker @@ -53,8 +41,7 @@ jobs: working-directory: crates/cli env: RUSTFLAGS: '-C target-cpu=x86-64-v2' - run: | - cargo build --release --target x86_64-unknown-linux-gnu --features jemalloc + run: cargo build --release --target x86_64-unknown-linux-gnu --features jemalloc - name: Prepare binary for Docker run: | @@ -62,23 +49,22 @@ jobs: cp target/x86_64-unknown-linux-gnu/release/rrelayer_cli docker-binary/rrelayer_cli chmod +x docker-binary/rrelayer_cli - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Log into registry ${{ env.REGISTRY }} + - name: Log into registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Generate SHA-based internal tag + - name: Generate tag id: tags run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) echo "tag=${{ env.IMAGE }}:sha-${SHORT_SHA}-amd64" >> $GITHUB_OUTPUT - - name: Build and push AMD64 Docker image + - name: Build and push AMD64 image uses: docker/build-push-action@v6 with: context: . @@ -88,8 +74,8 @@ jobs: platforms: linux/amd64 provenance: mode=max sbom: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=docker-amd64 + cache-to: type=gha,mode=max,scope=docker-amd64 build-arm64: name: Build ARM64 Docker Image @@ -98,19 +84,13 @@ jobs: contents: read packages: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Create docker-binary directory - run: mkdir -p docker-binary + - uses: actions/checkout@v4 - - name: Set up Rust toolchain - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@stable with: targets: aarch64-unknown-linux-gnu - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@v2 with: key: aarch64-unknown-linux-gnu-docker @@ -123,8 +103,7 @@ jobs: working-directory: crates/cli env: RUSTFLAGS: '-C target-cpu=neoverse-n1' - run: | - cargo build --release --target aarch64-unknown-linux-gnu + run: cargo build --release --target aarch64-unknown-linux-gnu - name: Prepare binary for Docker run: | @@ -132,23 +111,22 @@ jobs: cp target/aarch64-unknown-linux-gnu/release/rrelayer_cli docker-binary/rrelayer_cli chmod +x docker-binary/rrelayer_cli - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Log into registry ${{ env.REGISTRY }} + - name: Log into registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Generate SHA-based internal tag + - name: Generate tag id: tags run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) echo "tag=${{ env.IMAGE }}:sha-${SHORT_SHA}-arm64" >> $GITHUB_OUTPUT - - name: Build and push ARM64 Docker image + - name: Build and push ARM64 image uses: docker/build-push-action@v6 with: context: . @@ -158,8 +136,8 @@ jobs: platforms: linux/arm64 provenance: mode=max sbom: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=docker-arm64 + cache-to: type=gha,mode=max,scope=docker-arm64 create-manifest: name: Create Multi-Arch Manifest @@ -169,49 +147,37 @@ jobs: contents: read packages: write steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Log into registry ${{ env.REGISTRY }} + - name: Log into registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + VERSION="${{ github.event.release.tag_name }}" + echo "version=${VERSION#v}" >> $GITHUB_OUTPUT + elif [ -n "${{ inputs.version }}" ]; then + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + else + echo "version=" >> $GITHUB_OUTPUT + fi + - name: Create and push multi-arch manifest run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) - AMD64_IMAGE="${{ env.IMAGE }}:sha-${SHORT_SHA}-amd64" - ARM64_IMAGE="${{ env.IMAGE }}:sha-${SHORT_SHA}-arm64" - - echo "Source images:" - echo " AMD64: $AMD64_IMAGE" - echo " ARM64: $ARM64_IMAGE" - - VERSION="${{ inputs.version }}" - + AMD64="${{ env.IMAGE }}:sha-${SHORT_SHA}-amd64" + ARM64="${{ env.IMAGE }}:sha-${SHORT_SHA}-arm64" + VERSION="${{ steps.version.outputs.version }}" + if [ -n "$VERSION" ]; then - # Release build: create version tag and latest tag - echo "Creating release manifests for version: $VERSION" - - # Version tag (e.g., 1.2.3) - echo "Creating manifest for tag: $VERSION" - docker buildx imagetools create -t "${{ env.IMAGE }}:$VERSION" \ - "$AMD64_IMAGE" \ - "$ARM64_IMAGE" - - # Latest tag - echo "Creating manifest for tag: latest" - docker buildx imagetools create -t "${{ env.IMAGE }}:latest" \ - "$AMD64_IMAGE" \ - "$ARM64_IMAGE" + docker buildx imagetools create -t "${{ env.IMAGE }}:${VERSION}" "$AMD64" "$ARM64" + docker buildx imagetools create -t "${{ env.IMAGE }}:latest" "$AMD64" "$ARM64" else - # Non-release build (master): create master tag only - echo "Creating master manifest" - docker buildx imagetools create -t "${{ env.IMAGE }}:master" \ - "$AMD64_IMAGE" \ - "$ARM64_IMAGE" + docker buildx imagetools create -t "${{ env.IMAGE }}:edge" "$AMD64" "$ARM64" fi - - echo "Manifests created successfully" diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4389fb49..ad8842c0 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -34,7 +34,7 @@ pub use wallet::{generate_seed_phrase, AwsKmsWalletManager, WalletError}; mod background_tasks; mod rate_limiting; pub use rate_limiting::RATE_LIMIT_HEADER_NAME; -mod webhooks; +pub mod webhooks; mod yaml; pub use docker::generate_docker_file; diff --git a/crates/core/src/webhooks/mod.rs b/crates/core/src/webhooks/mod.rs index c3330ccb..af39ded1 100644 --- a/crates/core/src/webhooks/mod.rs +++ b/crates/core/src/webhooks/mod.rs @@ -5,6 +5,10 @@ pub use manager::WebhookManager; mod low_balance_payload; mod payload; -pub use low_balance_payload::WebhookLowBalancePayload; +pub use low_balance_payload::{WebhookBalanceAlertData, WebhookLowBalancePayload}; +pub use payload::{ + WebhookPayload, WebhookSigningData, WebhookSigningPayload, WebhookTransactionData, +}; mod sender; mod types; +pub use types::WebhookEventType; diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index f788e041..2813b208 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -35,4 +35,8 @@ pub use rrelayer_core::{ TransactionValue, }, }, + webhooks::{ + WebhookBalanceAlertData, WebhookEventType, WebhookLowBalancePayload, WebhookPayload, + WebhookSigningData, WebhookSigningPayload, WebhookTransactionData, + }, }; diff --git a/documentation/rrelayer/docs/pages/changelog.mdx b/documentation/rrelayer/docs/pages/changelog.mdx index 32801244..5ff48493 100644 --- a/documentation/rrelayer/docs/pages/changelog.mdx +++ b/documentation/rrelayer/docs/pages/changelog.mdx @@ -6,10 +6,14 @@ ### Features +- feat: expose webhook types (WebhookEventType, WebhookPayload, WebhookSigningPayload, WebhookLowBalancePayload) to Rust and TypeScript SDKs + --- ### Bug fixes +- chore: simplify CI/CD pipeline — Linux-only PR testing, Docker-only releases (no mac/windows binaries) + --- ### Breaking changes diff --git a/documentation/rrelayer/docs/pages/integration/sdk/webhooks/node.mdx b/documentation/rrelayer/docs/pages/integration/sdk/webhooks/node.mdx new file mode 100644 index 00000000..44406b06 --- /dev/null +++ b/documentation/rrelayer/docs/pages/integration/sdk/webhooks/node.mdx @@ -0,0 +1,186 @@ +# Webhook Types + +The TypeScript SDK exports all webhook payload types so you can deserialize and type-check incoming webhook payloads. + +## Event Types + +```ts +import { WebhookEventType } from 'rrelayer'; + +export enum WebhookEventType { + TransactionQueued = 'transaction_queued', + TransactionSent = 'transaction_sent', + TransactionMined = 'transaction_mined', + TransactionConfirmed = 'transaction_confirmed', + TransactionFailed = 'transaction_failed', + TransactionExpired = 'transaction_expired', + TransactionCancelled = 'transaction_cancelled', + TransactionReplaced = 'transaction_replaced', + TextSigned = 'text_signed', + TypedDataSigned = 'typed_data_signed', + LowBalance = 'low_balance', +} +``` + +## Transaction Webhook Payload + +```ts +import { WebhookPayload, WebhookTransactionData } from 'rrelayer'; + +export interface WebhookTransactionData { + id: string; + relayerId: string; + to: `0x${string}`; + from: `0x${string}`; + value: string; + data: string; + chainId: number; + status: string; + txHash?: string | null; + queuedAt: string; + sentAt?: string | null; + confirmedAt?: string | null; + expiresAt: string; + externalId?: string | null; + blobs?: string[] | null; + nonce: string; + minedAt?: string | null; + minedAtBlockNumber?: string | null; + maxPriorityFee?: string | null; + maxFee?: string | null; + isNoop: boolean; +} + +export interface WebhookPayload { + eventType: WebhookEventType; + transaction: WebhookTransactionData; + timestamp: string; + apiVersion: string; + originalTransaction?: WebhookTransactionData | null; + receipt?: Record | null; +} +``` + +## Signing Webhook Payload + +```ts +import { WebhookSigningPayload, WebhookSigningData } from 'rrelayer'; + +export interface WebhookSigningData { + relayerId: string; + chainId: number; + signature: string; + signedAt: string; + message?: string | null; + domainData?: Record | null; + messageData?: Record | null; + primaryType?: string | null; +} + +export interface WebhookSigningPayload { + eventType: WebhookEventType; + signing: WebhookSigningData; + timestamp: string; + apiVersion: string; +} +``` + +## Low Balance Webhook Payload + +```ts +import { WebhookLowBalancePayload, WebhookBalanceAlertData } from 'rrelayer'; + +export interface WebhookBalanceAlertData { + relayerId: string; + address: `0x${string}`; + chainId: number; + currentBalance: string; + minimumBalance: string; + currentBalanceFormatted: string; + minimumBalanceFormatted: string; + detectedAt: string; +} + +export interface WebhookLowBalancePayload { + eventType: WebhookEventType; + balanceAlert: WebhookBalanceAlertData; + timestamp: string; + apiVersion: string; +} +``` + +## Usage Examples + +### Handling Transaction Webhooks + +```ts +import express from 'express'; +import { + WebhookPayload, + WebhookEventType, +} from 'rrelayer'; + +const app = express(); +app.use(express.json()); + +app.post('/webhooks/transactions', (req, res) => { + const payload = req.body as WebhookPayload; + + switch (payload.eventType) { + case WebhookEventType.TransactionMined: + console.log(`Transaction ${payload.transaction.id} mined at block ${payload.transaction.minedAtBlockNumber}`); + break; + case WebhookEventType.TransactionFailed: + console.log(`Transaction ${payload.transaction.id} failed`); + break; + case WebhookEventType.TransactionReplaced: + console.log(`Transaction replaced. New: ${payload.transaction.id}, Original: ${payload.originalTransaction?.id}`); + break; + } + + res.status(200).json({ received: true }); +}); +``` + +### Handling Signing Webhooks + +```ts +import { + WebhookSigningPayload, + WebhookEventType, +} from 'rrelayer'; + +app.post('/webhooks/signing', (req, res) => { + const payload = req.body as WebhookSigningPayload; + + if (payload.eventType === WebhookEventType.TextSigned) { + console.log(`Message signed by relayer ${payload.signing.relayerId}: "${payload.signing.message}"`); + } else if (payload.eventType === WebhookEventType.TypedDataSigned) { + console.log(`Typed data signed, primary type: ${payload.signing.primaryType}`); + } + + res.status(200).json({ received: true }); +}); +``` + +### Handling Low Balance Alerts + +```ts +import { + WebhookLowBalancePayload, + WebhookEventType, +} from 'rrelayer'; + +app.post('/webhooks/alerts', (req, res) => { + const payload = req.body as WebhookLowBalancePayload; + + if (payload.eventType === WebhookEventType.LowBalance) { + const { relayerId, chainId, currentBalanceFormatted, minimumBalanceFormatted } = payload.balanceAlert; + console.log( + `Low balance alert: Relayer ${relayerId} on chain ${chainId} has ${currentBalanceFormatted} ETH (minimum: ${minimumBalanceFormatted} ETH)` + ); + } + + res.status(200).json({ received: true }); +}); +``` \ No newline at end of file diff --git a/documentation/rrelayer/docs/pages/integration/sdk/webhooks/rust.mdx b/documentation/rrelayer/docs/pages/integration/sdk/webhooks/rust.mdx new file mode 100644 index 00000000..9bbe2a1d --- /dev/null +++ b/documentation/rrelayer/docs/pages/integration/sdk/webhooks/rust.mdx @@ -0,0 +1,227 @@ +# Webhook Types + +The Rust SDK re-exports all webhook payload types from `rrelayer_core` so you can deserialize and type-check incoming webhook payloads. + +## Event Types + +```rust +use rrelayer::{WebhookEventType}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum WebhookEventType { + TransactionQueued, + TransactionSent, + TransactionMined, + TransactionConfirmed, + TransactionFailed, + TransactionExpired, + TransactionCancelled, + TransactionReplaced, + TextSigned, + TypedDataSigned, + LowBalance, +} +``` + +## Transaction Webhook Payload + +```rust +use rrelayer::{WebhookPayload, WebhookTransactionData}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebhookPayload { + pub event_type: WebhookEventType, + pub transaction: WebhookTransactionData, + pub timestamp: DateTime, + pub api_version: String, + pub original_transaction: Option, + pub receipt: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebhookTransactionData { + pub id: TransactionId, + pub relayer_id: RelayerId, + pub to: EvmAddress, + pub from: EvmAddress, + pub value: TransactionValue, + pub data: TransactionData, + pub chain_id: ChainId, + pub status: TransactionStatus, + pub transaction_hash: Option, + pub queued_at: DateTime, + pub sent_at: Option>, + pub confirmed_at: Option>, + pub expires_at: DateTime, + pub external_id: Option, + pub blobs: Option>, + pub nonce: TransactionNonce, + pub mined_at: Option>, + pub mined_at_block_number: Option, + pub sent_with_max_priority_fee_per_gas: Option, + pub sent_with_max_fee_per_gas: Option, + pub is_noop: bool, +} +``` + +## Signing Webhook Payload + +```rust +use rrelayer::{WebhookSigningPayload, WebhookSigningData}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebhookSigningPayload { + pub event_type: WebhookEventType, + pub signing: WebhookSigningData, + pub timestamp: DateTime, + pub api_version: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebhookSigningData { + pub relayer_id: RelayerId, + pub chain_id: ChainId, + pub signature: Signature, + pub signed_at: DateTime, + pub message: Option, + pub domain_data: Option, + pub message_data: Option, + pub primary_type: Option, +} +``` + +## Low Balance Webhook Payload + +```rust +use rrelayer::{WebhookLowBalancePayload, WebhookBalanceAlertData}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebhookLowBalancePayload { + pub event_type: WebhookEventType, + pub balance_alert: WebhookBalanceAlertData, + pub timestamp: DateTime, + pub api_version: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebhookBalanceAlertData { + pub relayer_id: String, + pub address: EvmAddress, + pub chain_id: ChainId, + pub current_balance: String, + pub minimum_balance: String, + pub current_balance_formatted: String, + pub minimum_balance_formatted: String, + pub detected_at: DateTime, +} +``` + +## Usage Examples + +### Deserializing Transaction Webhooks + +```rust +use rrelayer::{WebhookPayload, WebhookEventType}; + +async fn handle_webhook(body: String) -> Result<(), Box> { + let payload: WebhookPayload = serde_json::from_str(&body)?; + + match payload.event_type { + WebhookEventType::TransactionMined => { + println!( + "Transaction {} mined at block {:?}", + payload.transaction.id, + payload.transaction.mined_at_block_number + ); + } + WebhookEventType::TransactionFailed => { + println!("Transaction {} failed", payload.transaction.id); + } + WebhookEventType::TransactionReplaced => { + if let Some(original) = &payload.original_transaction { + println!( + "Transaction replaced. New: {}, Original: {}", + payload.transaction.id, original.id + ); + } + } + _ => {} + } + + Ok(()) +} +``` + +### Deserializing Signing Webhooks + +```rust +use rrelayer::{WebhookSigningPayload, WebhookEventType}; + +async fn handle_signing_webhook(body: String) -> Result<(), Box> { + let payload: WebhookSigningPayload = serde_json::from_str(&body)?; + + match payload.event_type { + WebhookEventType::TextSigned => { + println!( + "Message signed by relayer {}: {:?}", + payload.signing.relayer_id, + payload.signing.message + ); + } + WebhookEventType::TypedDataSigned => { + println!( + "Typed data signed, primary type: {:?}", + payload.signing.primary_type + ); + } + _ => {} + } + + Ok(()) +} +``` + +### Deserializing Low Balance Alerts + +```rust +use rrelayer::{WebhookLowBalancePayload, WebhookEventType}; + +async fn handle_balance_alert(body: String) -> Result<(), Box> { + let payload: WebhookLowBalancePayload = serde_json::from_str(&body)?; + + if payload.event_type == WebhookEventType::LowBalance { + let alert = &payload.balance_alert; + println!( + "Low balance: Relayer {} on chain {} has {} ETH (minimum: {} ETH)", + alert.relayer_id, + alert.chain_id, + alert.current_balance_formatted, + alert.minimum_balance_formatted + ); + } + + Ok(()) +} +``` + +### Using with Axum + +```rust +use axum::{Json, http::StatusCode}; +use rrelayer::{WebhookPayload, WebhookEventType}; + +async fn webhook_handler( + Json(payload): Json, +) -> StatusCode { + match payload.event_type { + WebhookEventType::TransactionConfirmed => { + // Process confirmed transaction + println!("Confirmed: {}", payload.transaction.id); + } + _ => {} + } + + StatusCode::OK +} +``` \ No newline at end of file diff --git a/documentation/rrelayer/vocs.config.tsx b/documentation/rrelayer/vocs.config.tsx index eb3f37a0..a1f516ed 100644 --- a/documentation/rrelayer/vocs.config.tsx +++ b/documentation/rrelayer/vocs.config.tsx @@ -502,6 +502,66 @@ export default defineConfig({ }, ], }, + { + text: 'Webhooks', + collapsed: true, + items: [ + { + text: 'Node', + collapsed: true, + link: '/integration/sdk/webhooks/node', + items: [ + { + text: 'Event Types', + link: '/integration/sdk/webhooks/node#event-types', + }, + { + text: 'Transaction Payload', + link: '/integration/sdk/webhooks/node#transaction-webhook-payload', + }, + { + text: 'Signing Payload', + link: '/integration/sdk/webhooks/node#signing-webhook-payload', + }, + { + text: 'Low Balance Payload', + link: '/integration/sdk/webhooks/node#low-balance-webhook-payload', + }, + { + text: 'Usage Examples', + link: '/integration/sdk/webhooks/node#usage-examples', + }, + ], + }, + { + text: 'Rust', + collapsed: true, + link: '/integration/sdk/webhooks/rust', + items: [ + { + text: 'Event Types', + link: '/integration/sdk/webhooks/rust#event-types', + }, + { + text: 'Transaction Payload', + link: '/integration/sdk/webhooks/rust#transaction-webhook-payload', + }, + { + text: 'Signing Payload', + link: '/integration/sdk/webhooks/rust#signing-webhook-payload', + }, + { + text: 'Low Balance Payload', + link: '/integration/sdk/webhooks/rust#low-balance-webhook-payload', + }, + { + text: 'Usage Examples', + link: '/integration/sdk/webhooks/rust#usage-examples', + }, + ], + }, + ], + }, { text: 'Sign', collapsed: true, diff --git a/sdk/typescript/package-lock.json b/sdk/typescript/package-lock.json index d5ce4dda..f95efc8c 100644 --- a/sdk/typescript/package-lock.json +++ b/sdk/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "rrelayer", - "version": "1.0.7", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rrelayer", - "version": "1.0.7", + "version": "1.2.0", "license": "MIT", "dependencies": { "axios": "^1.6.7", diff --git a/sdk/typescript/src/api/index.ts b/sdk/typescript/src/api/index.ts index a7e63173..60326f8c 100644 --- a/sdk/typescript/src/api/index.ts +++ b/sdk/typescript/src/api/index.ts @@ -3,5 +3,6 @@ export * from './network'; export * from './relayer'; export * from './transaction'; export * from './signing'; +export * from './webhook'; export const RATE_LIMIT_HEADER_NAME = 'x-rrelayer-rate-limit-key'; diff --git a/sdk/typescript/src/api/webhook/index.ts b/sdk/typescript/src/api/webhook/index.ts new file mode 100644 index 00000000..fcb073fe --- /dev/null +++ b/sdk/typescript/src/api/webhook/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/sdk/typescript/src/api/webhook/types.ts b/sdk/typescript/src/api/webhook/types.ts new file mode 100644 index 00000000..f31e3e4c --- /dev/null +++ b/sdk/typescript/src/api/webhook/types.ts @@ -0,0 +1,82 @@ +export enum WebhookEventType { + TransactionQueued = 'transaction_queued', + TransactionSent = 'transaction_sent', + TransactionMined = 'transaction_mined', + TransactionConfirmed = 'transaction_confirmed', + TransactionFailed = 'transaction_failed', + TransactionExpired = 'transaction_expired', + TransactionCancelled = 'transaction_cancelled', + TransactionReplaced = 'transaction_replaced', + TextSigned = 'text_signed', + TypedDataSigned = 'typed_data_signed', + LowBalance = 'low_balance', +} + +export interface WebhookTransactionData { + id: string; + relayerId: string; + to: `0x${string}`; + from: `0x${string}`; + value: string; + data: string; + chainId: number; + status: string; + txHash?: string | null; + queuedAt: string; + sentAt?: string | null; + confirmedAt?: string | null; + expiresAt: string; + externalId?: string | null; + blobs?: string[] | null; + nonce: string; + minedAt?: string | null; + minedAtBlockNumber?: string | null; + maxPriorityFee?: string | null; + maxFee?: string | null; + isNoop: boolean; +} + +export interface WebhookPayload { + eventType: WebhookEventType; + transaction: WebhookTransactionData; + timestamp: string; + apiVersion: string; + originalTransaction?: WebhookTransactionData | null; + receipt?: Record | null; +} + +export interface WebhookSigningData { + relayerId: string; + chainId: number; + signature: string; + signedAt: string; + message?: string | null; + domainData?: Record | null; + messageData?: Record | null; + primaryType?: string | null; +} + +export interface WebhookSigningPayload { + eventType: WebhookEventType; + signing: WebhookSigningData; + timestamp: string; + apiVersion: string; +} + +export interface WebhookBalanceAlertData { + relayerId: string; + address: `0x${string}`; + chainId: number; + currentBalance: string; + minimumBalance: string; + currentBalanceFormatted: string; + minimumBalanceFormatted: string; + detectedAt: string; +} + +export interface WebhookLowBalancePayload { + eventType: WebhookEventType; + balanceAlert: WebhookBalanceAlertData; + timestamp: string; + apiVersion: string; +}