From 5d618043897d4eb5bcf91d069756e9dd1dace11a Mon Sep 17 00:00:00 2001 From: Dominik Harz Date: Thu, 26 Mar 2026 14:34:18 +0100 Subject: [PATCH 1/2] feat(sdk): add gateway utility helpers for CLI and bot consumers - Add getInnerQuote() to unwrap discriminated GatewayQuote union - Add InnerQuote type (union of onramp/offramp/layerZero quotes) - Add ScureBitcoinSigner re-export from utils for direct PSBT signing - Add MempoolClient.getAddressMempoolTxs for mempool tx monitoring - Export getBalance and estimateTxFee from wallet utilities These helpers support the upcoming gateway-cli tool and other bot consumers that need lower-level access to quote discrimination and Bitcoin signing. Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/src/gateway/index.ts | 12 ++++++++-- sdk/src/gateway/utils/bitcoin-signer.ts | 30 +++++++++++++++++++++++++ sdk/src/gateway/utils/index.ts | 1 + sdk/src/gateway/utils/quote.ts | 30 +++++++++++++++++++++++++ sdk/src/mempool.ts | 14 ++++++++++++ sdk/src/wallet/index.ts | 6 ++++- 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 sdk/src/gateway/utils/quote.ts diff --git a/sdk/src/gateway/index.ts b/sdk/src/gateway/index.ts index 6f84d4de8..5996047c2 100644 --- a/sdk/src/gateway/index.ts +++ b/sdk/src/gateway/index.ts @@ -2,5 +2,13 @@ export { OkxWalletAdapter } from './adapters/okx-wallet'; export { ReownWalletAdapter } from './adapters/reown'; export { ExecuteQuoteResult, GatewayApiClient as GatewaySDK } from './client'; export * from './generated-client'; -export { GatewayQuoteParams, GetQuoteParams } from './types'; -export { formatBtc, parseBtc } from './utils'; +export { BitcoinSigner, GatewayQuoteParams, GetQuoteParams } from './types'; +export { + formatBtc, + parseBtc, + ScureBitcoinSigner, + supportedChainsMapping, + getChainConfig, + getInnerQuote, + type InnerQuote, +} from './utils'; diff --git a/sdk/src/gateway/utils/bitcoin-signer.ts b/sdk/src/gateway/utils/bitcoin-signer.ts index f7f6de687..caa348705 100644 --- a/sdk/src/gateway/utils/bitcoin-signer.ts +++ b/sdk/src/gateway/utils/bitcoin-signer.ts @@ -15,6 +15,36 @@ export class ScureBitcoinSigner implements BitcoinSigner { this.privateKey = new Uint8Array(Buffer.from(cleanPrivateKey, 'hex')); } + /** + * Create a Bitcoin signer from a WIF-encoded private key + * @param wif WIF-encoded private key string + * @returns A new ScureBitcoinSigner instance + */ + static fromWIF(wif: string): ScureBitcoinSigner { + const decoded = btc.WIF().decode(wif); + return new ScureBitcoinSigner(Buffer.from(decoded).toString('hex')); + } + + /** + * Create a Bitcoin signer from a private key in any supported format (hex or WIF). + * Auto-detects the format. + * @param key Private key as hex string (with or without 0x prefix) or WIF-encoded + * @returns A new ScureBitcoinSigner instance + */ + static fromKey(key: string): ScureBitcoinSigner { + const stripped = key.startsWith('0x') ? key.slice(2) : key; + if (/^[0-9a-fA-F]{64}$/.test(stripped)) { + return new ScureBitcoinSigner(stripped); + } + try { + return ScureBitcoinSigner.fromWIF(key); + } catch (e) { + throw new Error('Invalid private key: expected 64-char hex (with optional 0x prefix) or WIF-encoded key', { + cause: e, + }); + } + } + /** * Create a Bitcoin signer from a seed phrase (BIP39 mnemonic) * @param seedPhrase The BIP39 mnemonic seed phrase diff --git a/sdk/src/gateway/utils/index.ts b/sdk/src/gateway/utils/index.ts index 12e4bda02..9410ccfc1 100644 --- a/sdk/src/gateway/utils/index.ts +++ b/sdk/src/gateway/utils/index.ts @@ -1,2 +1,3 @@ export * from './common'; export * from './bitcoin-signer'; +export * from './quote'; diff --git a/sdk/src/gateway/utils/quote.ts b/sdk/src/gateway/utils/quote.ts new file mode 100644 index 000000000..eab353601 --- /dev/null +++ b/sdk/src/gateway/utils/quote.ts @@ -0,0 +1,30 @@ +import type { GatewayQuote } from '../generated-client/models/GatewayQuote'; +import type { GatewayOnrampQuote } from '../generated-client/models/GatewayOnrampQuote'; +import type { GatewayOfframpQuote } from '../generated-client/models/GatewayOfframpQuote'; +import type { GatewayLayerZeroQuote } from '../generated-client/models/GatewayLayerZeroQuote'; +import { instanceOfGatewayQuoteOneOf } from '../generated-client/models/GatewayQuoteOneOf'; +import { instanceOfGatewayQuoteOneOf1 } from '../generated-client/models/GatewayQuoteOneOf1'; +import { instanceOfGatewayQuoteOneOf2 } from '../generated-client/models/GatewayQuoteOneOf2'; + +export type InnerQuote = GatewayOnrampQuote | GatewayOfframpQuote | GatewayLayerZeroQuote; + +/** + * Unwraps a GatewayQuote, returning the inner quote object directly. + * + * All three variants share common fields (inputAmount, outputAmount, fees, + * recipient, estimatedTimeInSecs, sender), so you can access them without + * caring about the variant: + * + * ```ts + * const inner = getInnerQuote(quote); + * console.log(inner.inputAmount.amount); + * console.log(inner.outputAmount.amount); + * console.log(inner.fees.amount); + * ``` + */ +export function getInnerQuote(quote: GatewayQuote): InnerQuote { + if (instanceOfGatewayQuoteOneOf(quote as object)) return (quote as any).onramp; + if (instanceOfGatewayQuoteOneOf1(quote as object)) return (quote as any).offramp; + if (instanceOfGatewayQuoteOneOf2(quote as object)) return (quote as any).layerZero; + throw new Error('Unknown quote variant'); +} diff --git a/sdk/src/mempool.ts b/sdk/src/mempool.ts index 6d7567a79..49d9cb6fd 100644 --- a/sdk/src/mempool.ts +++ b/sdk/src/mempool.ts @@ -252,6 +252,20 @@ export class MempoolClient { return Infinity; } + /** + * Get pending (unconfirmed) transactions for a Bitcoin address from the mempool. + * + * @param {string} address - The Bitcoin address to check. + * @returns {Promise} Array of unconfirmed transactions. + * + * @example + * const mempoolClient = new MempoolClient(); + * const pendingTxs = await mempoolClient.getAddressMempoolTxs('bc1q...'); + */ + async getAddressMempoolTxs(address: string): Promise { + return this.getJson(`${this.basePath}/address/${address}/txs/mempool`); + } + /** * @ignore */ diff --git a/sdk/src/wallet/index.ts b/sdk/src/wallet/index.ts index 861b3c711..8bc092ad4 100644 --- a/sdk/src/wallet/index.ts +++ b/sdk/src/wallet/index.ts @@ -1,3 +1,7 @@ export * from './utxo'; -export { validate as isValidBtcAddress } from 'bitcoin-address-validation'; +export { + validate as isValidBtcAddress, + getAddressInfo as getBtcAddressInfo, + AddressType as BtcAddressType, +} from 'bitcoin-address-validation'; From aa6f257506424446a0107c1cceb1fc163ea00022 Mon Sep 17 00:00:00 2001 From: Dominik Harz Date: Thu, 26 Mar 2026 14:45:14 +0100 Subject: [PATCH 2/2] =?UTF-8?q?fix(sdk):=20fix=20lint=20errors=20in=20getI?= =?UTF-8?q?nnerQuote=20=E2=80=94=20use=20proper=20type=20narrowing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/src/gateway/utils/quote.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sdk/src/gateway/utils/quote.ts b/sdk/src/gateway/utils/quote.ts index eab353601..a48b59605 100644 --- a/sdk/src/gateway/utils/quote.ts +++ b/sdk/src/gateway/utils/quote.ts @@ -2,9 +2,9 @@ import type { GatewayQuote } from '../generated-client/models/GatewayQuote'; import type { GatewayOnrampQuote } from '../generated-client/models/GatewayOnrampQuote'; import type { GatewayOfframpQuote } from '../generated-client/models/GatewayOfframpQuote'; import type { GatewayLayerZeroQuote } from '../generated-client/models/GatewayLayerZeroQuote'; -import { instanceOfGatewayQuoteOneOf } from '../generated-client/models/GatewayQuoteOneOf'; -import { instanceOfGatewayQuoteOneOf1 } from '../generated-client/models/GatewayQuoteOneOf1'; -import { instanceOfGatewayQuoteOneOf2 } from '../generated-client/models/GatewayQuoteOneOf2'; +import { instanceOfGatewayQuoteOneOf, type GatewayQuoteOneOf } from '../generated-client/models/GatewayQuoteOneOf'; +import { instanceOfGatewayQuoteOneOf1, type GatewayQuoteOneOf1 } from '../generated-client/models/GatewayQuoteOneOf1'; +import { instanceOfGatewayQuoteOneOf2, type GatewayQuoteOneOf2 } from '../generated-client/models/GatewayQuoteOneOf2'; export type InnerQuote = GatewayOnrampQuote | GatewayOfframpQuote | GatewayLayerZeroQuote; @@ -23,8 +23,9 @@ export type InnerQuote = GatewayOnrampQuote | GatewayOfframpQuote | GatewayLayer * ``` */ export function getInnerQuote(quote: GatewayQuote): InnerQuote { - if (instanceOfGatewayQuoteOneOf(quote as object)) return (quote as any).onramp; - if (instanceOfGatewayQuoteOneOf1(quote as object)) return (quote as any).offramp; - if (instanceOfGatewayQuoteOneOf2(quote as object)) return (quote as any).layerZero; + const q = quote as object; + if (instanceOfGatewayQuoteOneOf(q)) return (q as GatewayQuoteOneOf).onramp; + if (instanceOfGatewayQuoteOneOf1(q)) return (q as GatewayQuoteOneOf1).offramp; + if (instanceOfGatewayQuoteOneOf2(q)) return (q as GatewayQuoteOneOf2).layerZero; throw new Error('Unknown quote variant'); }