From 699ff8c6d24f877616efbc7e517410c8ed383657 Mon Sep 17 00:00:00 2001 From: "Peter.R" Date: Thu, 23 Jul 2026 00:48:34 +0000 Subject: [PATCH 1/4] feat(quotes): add Bitcoin L1 deposit-address instruction + refundAddress Add a Bitcoin L1 on/off-ramp surface to the quote/execute flow: - New self-contained PaymentBitcoinDepositAddressInfo member in the PaymentInstructions oneOf (accountType=BITCOIN_L1 discriminator value, on-chain address, optional network fixed to BITCOIN). - Optional refundAddress on RealtimeFundingQuoteSource (on-ramp crypto funding) and AccountDestination (off-ramp crypto payout). The member is self-contained rather than composing BasePaymentAccountInfo, so it does not extend the shared PaymentAccountType enum; the discriminator mapping is the source of truth for the BITCOIN_L1 value. All additions are optional / additive, so no version bump. SDKs are regenerated by the Stainless CI action from the bundled spec + config. Co-Authored-By: peterrojs --- mintlify/openapi.yaml | 33 +++++++++++++++++++ openapi.yaml | 33 +++++++++++++++++++ .../PaymentBitcoinDepositAddressInfo.yaml | 22 +++++++++++++ .../schemas/common/PaymentInstructions.yaml | 2 ++ .../schemas/quotes/AccountDestination.yaml | 6 ++++ .../quotes/RealtimeFundingQuoteSource.yaml | 7 ++++ 6 files changed, 103 insertions(+) create mode 100644 openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 7881e1f0b..009564524 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -14261,6 +14261,29 @@ components: description: Type of asset enum: - USDC + PaymentBitcoinDepositAddressInfo: + title: Bitcoin L1 Deposit Address + type: object + required: + - accountType + - address + properties: + accountType: + type: string + description: Type of payment account or wallet + enum: + - BITCOIN_L1 + example: BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN AedAccountInfoBase: type: object required: @@ -15641,6 +15664,7 @@ components: - $ref: '#/components/schemas/PaymentPolygonWalletInfo' - $ref: '#/components/schemas/PaymentBaseWalletInfo' - $ref: '#/components/schemas/PaymentEthereumWalletInfo' + - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo' - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo' discriminator: propertyName: accountType @@ -15668,6 +15692,7 @@ components: POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo' BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo' ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo' + BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo' AED_ACCOUNT: '#/components/schemas/PaymentAedAccountInfo' KES_ACCOUNT: '#/components/schemas/PaymentKesAccountInfo' MWK_ACCOUNT: '#/components/schemas/PaymentMwkAccountInfo' @@ -20484,6 +20509,10 @@ components: type: string description: 'The crypto network to use for the funding source. Required when `currency` is a stablecoin (e.g. USDC, USDT). Specifies which network the customer will deposit on, so the correct deposit address can be generated. Example values: `SOLANA`, `ETHEREUM`, `BASE`, `POLYGON`, `SPARK`, `LIGHTNING`, `BITCOIN`.' example: SOLANA + refundAddress: + type: string + description: Optional address the deposited funds are returned to if the crypto deposit cannot be routed to complete the payment. Applies to on-chain crypto funding sources. + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq description: Fund the quote using a real-time funding source (RTP, SEPA Instant, Spark, Stables, etc.). This will require manual just-in-time funding using `paymentInstructions` in the response. Because quotes expire quickly, this option is only valid for instant payment methods. Do not try to fund a quote with a non-instant payment method (ACH, etc.). QuoteSourceOneOf: oneOf: @@ -20529,6 +20558,10 @@ components: allOf: - $ref: '#/components/schemas/PaymentRail' - description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. + refundAddress: + type: string + description: Optional address the funds are returned to if the payout cannot be completed. Applies to crypto off-ramp destinations. + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq description: Destination account details UmaAddressDestination: title: UMA Address diff --git a/openapi.yaml b/openapi.yaml index 7881e1f0b..009564524 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -14261,6 +14261,29 @@ components: description: Type of asset enum: - USDC + PaymentBitcoinDepositAddressInfo: + title: Bitcoin L1 Deposit Address + type: object + required: + - accountType + - address + properties: + accountType: + type: string + description: Type of payment account or wallet + enum: + - BITCOIN_L1 + example: BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN AedAccountInfoBase: type: object required: @@ -15641,6 +15664,7 @@ components: - $ref: '#/components/schemas/PaymentPolygonWalletInfo' - $ref: '#/components/schemas/PaymentBaseWalletInfo' - $ref: '#/components/schemas/PaymentEthereumWalletInfo' + - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo' - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo' discriminator: propertyName: accountType @@ -15668,6 +15692,7 @@ components: POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo' BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo' ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo' + BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo' AED_ACCOUNT: '#/components/schemas/PaymentAedAccountInfo' KES_ACCOUNT: '#/components/schemas/PaymentKesAccountInfo' MWK_ACCOUNT: '#/components/schemas/PaymentMwkAccountInfo' @@ -20484,6 +20509,10 @@ components: type: string description: 'The crypto network to use for the funding source. Required when `currency` is a stablecoin (e.g. USDC, USDT). Specifies which network the customer will deposit on, so the correct deposit address can be generated. Example values: `SOLANA`, `ETHEREUM`, `BASE`, `POLYGON`, `SPARK`, `LIGHTNING`, `BITCOIN`.' example: SOLANA + refundAddress: + type: string + description: Optional address the deposited funds are returned to if the crypto deposit cannot be routed to complete the payment. Applies to on-chain crypto funding sources. + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq description: Fund the quote using a real-time funding source (RTP, SEPA Instant, Spark, Stables, etc.). This will require manual just-in-time funding using `paymentInstructions` in the response. Because quotes expire quickly, this option is only valid for instant payment methods. Do not try to fund a quote with a non-instant payment method (ACH, etc.). QuoteSourceOneOf: oneOf: @@ -20529,6 +20558,10 @@ components: allOf: - $ref: '#/components/schemas/PaymentRail' - description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. + refundAddress: + type: string + description: Optional address the funds are returned to if the payout cannot be completed. Applies to crypto off-ramp destinations. + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq description: Destination account details UmaAddressDestination: title: UMA Address diff --git a/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml b/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml new file mode 100644 index 000000000..458fe080f --- /dev/null +++ b/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml @@ -0,0 +1,22 @@ +title: Bitcoin L1 Deposit Address +type: object +required: + - accountType + - address +properties: + accountType: + type: string + description: Type of payment account or wallet + enum: + - BITCOIN_L1 + example: BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN diff --git a/openapi/components/schemas/common/PaymentInstructions.yaml b/openapi/components/schemas/common/PaymentInstructions.yaml index c4abe811f..18c2ac9fa 100644 --- a/openapi/components/schemas/common/PaymentInstructions.yaml +++ b/openapi/components/schemas/common/PaymentInstructions.yaml @@ -60,6 +60,7 @@ properties: - $ref: ../common/PaymentPolygonWalletInfo.yaml - $ref: ../common/PaymentBaseWalletInfo.yaml - $ref: ../common/PaymentEthereumWalletInfo.yaml + - $ref: ../common/PaymentBitcoinDepositAddressInfo.yaml - $ref: ../common/PaymentEmbeddedWalletInfo.yaml discriminator: propertyName: accountType @@ -87,6 +88,7 @@ properties: POLYGON_WALLET: ../common/PaymentPolygonWalletInfo.yaml BASE_WALLET: ../common/PaymentBaseWalletInfo.yaml ETHEREUM_WALLET: ../common/PaymentEthereumWalletInfo.yaml + BITCOIN_L1: ../common/PaymentBitcoinDepositAddressInfo.yaml AED_ACCOUNT: ../common/PaymentAedAccountInfo.yaml KES_ACCOUNT: ../common/PaymentKesAccountInfo.yaml MWK_ACCOUNT: ../common/PaymentMwkAccountInfo.yaml diff --git a/openapi/components/schemas/quotes/AccountDestination.yaml b/openapi/components/schemas/quotes/AccountDestination.yaml index ea6c24d85..2a6fbecdc 100644 --- a/openapi/components/schemas/quotes/AccountDestination.yaml +++ b/openapi/components/schemas/quotes/AccountDestination.yaml @@ -21,4 +21,10 @@ allOf: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. + refundAddress: + type: string + description: >- + Optional address the funds are returned to if the payout cannot be + completed. Applies to crypto off-ramp destinations. + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq description: Destination account details diff --git a/openapi/components/schemas/quotes/RealtimeFundingQuoteSource.yaml b/openapi/components/schemas/quotes/RealtimeFundingQuoteSource.yaml index 6793b4ac7..d532022c1 100644 --- a/openapi/components/schemas/quotes/RealtimeFundingQuoteSource.yaml +++ b/openapi/components/schemas/quotes/RealtimeFundingQuoteSource.yaml @@ -31,6 +31,13 @@ allOf: on, so the correct deposit address can be generated. Example values: `SOLANA`, `ETHEREUM`, `BASE`, `POLYGON`, `SPARK`, `LIGHTNING`, `BITCOIN`. example: SOLANA + refundAddress: + type: string + description: >- + Optional address the deposited funds are returned to if the crypto + deposit cannot be routed to complete the payment. Applies to on-chain + crypto funding sources. + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq description: >- Fund the quote using a real-time funding source (RTP, SEPA Instant, Spark, Stables, etc.). This will require manual just-in-time funding using `paymentInstructions` in the response. From 1f5f79ee0b13f90d388102390d228a84ebcc14d8 Mon Sep 17 00:00:00 2001 From: "Peter.Rojs" Date: Tue, 28 Jul 2026 19:54:22 +0000 Subject: [PATCH 2/4] fix(quotes): model Bitcoin L1 instruction like its cousins Add BITCOIN_L1 to the shared PaymentAccountType enum and have PaymentBitcoinDepositAddressInfo inherit BasePaymentAccountInfo, narrowing accountType to BITCOIN_L1 as every other payment-instruction member does. Co-Authored-By: akanter Co-Authored-By: Claude Opus 5 (1M context) --- mintlify/openapi.yaml | 42 +++++++++---------- openapi.yaml | 42 +++++++++---------- .../schemas/common/PaymentAccountType.yaml | 1 + .../PaymentBitcoinDepositAddressInfo.yaml | 41 +++++++++--------- 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 009564524..e30b42058 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -13175,6 +13175,7 @@ components: - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET + - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT BasePaymentAccountInfo: @@ -14263,27 +14264,26 @@ components: - USDC PaymentBitcoinDepositAddressInfo: title: Bitcoin L1 Deposit Address - type: object - required: - - accountType - - address - properties: - accountType: - type: string - description: Type of payment account or wallet - enum: - - BITCOIN_L1 - example: BITCOIN_L1 - address: - type: string - description: On-chain Bitcoin (L1) deposit address to send funds to - example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq - network: - type: string - description: The blockchain network for the deposit address. - enum: - - BITCOIN - example: BITCOIN + allOf: + - $ref: '#/components/schemas/BasePaymentAccountInfo' + - type: object + required: + - address + properties: + accountType: + type: string + enum: + - BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN AedAccountInfoBase: type: object required: diff --git a/openapi.yaml b/openapi.yaml index 009564524..e30b42058 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -13175,6 +13175,7 @@ components: - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET + - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT BasePaymentAccountInfo: @@ -14263,27 +14264,26 @@ components: - USDC PaymentBitcoinDepositAddressInfo: title: Bitcoin L1 Deposit Address - type: object - required: - - accountType - - address - properties: - accountType: - type: string - description: Type of payment account or wallet - enum: - - BITCOIN_L1 - example: BITCOIN_L1 - address: - type: string - description: On-chain Bitcoin (L1) deposit address to send funds to - example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq - network: - type: string - description: The blockchain network for the deposit address. - enum: - - BITCOIN - example: BITCOIN + allOf: + - $ref: '#/components/schemas/BasePaymentAccountInfo' + - type: object + required: + - address + properties: + accountType: + type: string + enum: + - BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN AedAccountInfoBase: type: object required: diff --git a/openapi/components/schemas/common/PaymentAccountType.yaml b/openapi/components/schemas/common/PaymentAccountType.yaml index 30ff1bc1c..5327703bf 100644 --- a/openapi/components/schemas/common/PaymentAccountType.yaml +++ b/openapi/components/schemas/common/PaymentAccountType.yaml @@ -26,5 +26,6 @@ enum: - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET + - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT diff --git a/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml b/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml index 458fe080f..7fbc4c525 100644 --- a/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml +++ b/openapi/components/schemas/common/PaymentBitcoinDepositAddressInfo.yaml @@ -1,22 +1,21 @@ title: Bitcoin L1 Deposit Address -type: object -required: - - accountType - - address -properties: - accountType: - type: string - description: Type of payment account or wallet - enum: - - BITCOIN_L1 - example: BITCOIN_L1 - address: - type: string - description: On-chain Bitcoin (L1) deposit address to send funds to - example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq - network: - type: string - description: The blockchain network for the deposit address. - enum: - - BITCOIN - example: BITCOIN +allOf: + - $ref: ./BasePaymentAccountInfo.yaml + - type: object + required: + - address + properties: + accountType: + type: string + enum: + - BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN From 298add7c4a604f2e8791fc5e6b3da988146d0da0 Mon Sep 17 00:00:00 2001 From: Peter Rojs Date: Wed, 29 Jul 2026 00:24:08 +0100 Subject: [PATCH 3/4] fix --- .github/workflows/openapi-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/openapi-breaking-changes.yml b/.github/workflows/openapi-breaking-changes.yml index fc3530a31..4d39bbead 100644 --- a/.github/workflows/openapi-breaking-changes.yml +++ b/.github/workflows/openapi-breaking-changes.yml @@ -108,7 +108,7 @@ jobs: uses: marocchino/sticky-pull-request-comment@v2 with: header: oasdiff-breaking-changes - message: ${{ steps.body.outputs.body }} + path: breaking.md - name: Clear PR comment (no breaking) if: steps.oasdiff.outputs.has_breaking == 'false' From 380105ad9b2431b404d89951cd65e67769043260 Mon Sep 17 00:00:00 2001 From: "Peter.Rojs" Date: Tue, 28 Jul 2026 23:35:20 +0000 Subject: [PATCH 4/4] ci: bound the oasdiff breaking-changes PR comment Co-Authored-By: peterrojs --- .../workflows/openapi-breaking-changes.yml | 86 +++++++++++++++++-- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/.github/workflows/openapi-breaking-changes.yml b/.github/workflows/openapi-breaking-changes.yml index 4d39bbead..3aaf7b751 100644 --- a/.github/workflows/openapi-breaking-changes.yml +++ b/.github/workflows/openapi-breaking-changes.yml @@ -52,6 +52,20 @@ jobs: --format markdown \ --fail-on ERR > breaking.md status=$? + # The markdown format prints every finding with a :warning: marker, so it + # cannot be filtered by severity; singleline prefixes each with its level. + oasdiff breaking \ + base/openapi.yaml head/openapi.yaml \ + --format singleline \ + --fail-on ERR > breaking.txt + singleline_status=$? + set -e + # Both runs diff the same specs with the same --fail-on, so their exit codes + # must agree; anything above 1 is a crash rather than a finding. + if [ "$status" -gt 1 ] || [ "$singleline_status" -ne "$status" ]; then + echo "::error::oasdiff exited with $status (markdown) / $singleline_status (singleline)" + exit 1 + fi echo "status=$status" >> "$GITHUB_OUTPUT" if [ "$status" -ne 0 ] && [ -s breaking.md ]; then echo "has_breaking=true" >> "$GITHUB_OUTPUT" @@ -60,21 +74,77 @@ jobs: fi - name: Build comment body - id: body if: steps.oasdiff.outputs.has_breaking == 'true' + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | + # GitHub rejects a comment body over 65536 characters, and one change to a + # shared schema yields a finding per inheriting subschema per endpoint. + MAX_ERRORS=100 + MAX_WARNINGS=25 + MAX_BODY=64000 + + errors=$(grep -c '^error ' breaking.txt || true) + warnings=$(grep -c '^warning ' breaking.txt || true) + # `error at head/openapi.yaml, in API GET /quotes added …` -> `- `GET /quotes` — added …` + fmt() { sed 's/^\(error\|warning\) at [^,]*, in API \([A-Z]* [^ ]*\) /- `\2` — /'; } + { - echo 'body<Show sample' + echo '' + grep -m "$MAX_WARNINGS" '^warning ' breaking.txt | fmt + if [ "$warnings" -gt "$MAX_WARNINGS" ]; then + echo "- _…and $((warnings - MAX_WARNINGS)) more warnings._" + fi + echo '' + echo '' + fi echo '' echo '---' - echo '_Detected by [oasdiff](https://github.com/oasdiff/oasdiff). This PR will need approval from an API reviewer before merge._' - echo 'COMMENT_EOF' - } >> "$GITHUB_OUTPUT" + echo "_Detected by [oasdiff](https://github.com/oasdiff/oasdiff). Full report: [job summary]($RUN_URL) or the \`oasdiff-report\` artifact._" + } > comment.md + + if [ "$(wc -c < comment.md)" -gt "$MAX_BODY" ]; then + # Trim on line boundaries so a multi-byte character is never split. + LC_ALL=C awk -v max="$MAX_BODY" '{ n += length($0) + 1; if (n > max) exit } 1' \ + comment.md > comment.trimmed + printf '\n_Comment truncated. Full report: [job summary](%s)._\n' "$RUN_URL" >> comment.trimmed + mv comment.trimmed comment.md + fi + + - name: Publish full report to job summary + if: steps.oasdiff.outputs.has_breaking == 'true' + run: | + # The step summary is capped at 1 MiB. + { + echo '## oasdiff breaking changes' + echo '' + head -c 900000 breaking.md + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload full report + if: steps.oasdiff.outputs.has_breaking == 'true' + uses: actions/upload-artifact@v4 + with: + name: oasdiff-report + path: | + breaking.md + breaking.txt - name: Ensure breaking-change label exists if: steps.oasdiff.outputs.has_breaking == 'true' @@ -108,7 +178,7 @@ jobs: uses: marocchino/sticky-pull-request-comment@v2 with: header: oasdiff-breaking-changes - path: breaking.md + path: comment.md - name: Clear PR comment (no breaking) if: steps.oasdiff.outputs.has_breaking == 'false'