Skip to content

@web3settle/merchant-sdk v0.5.0 — gas estimator, telemetry, headless, permit#7

Merged
marceloceccon merged 10 commits intomainfrom
develop
May 9, 2026
Merged

@web3settle/merchant-sdk v0.5.0 — gas estimator, telemetry, headless, permit#7
marceloceccon merged 10 commits intomainfrom
develop

Conversation

@marceloceccon
Copy link
Copy Markdown
Member

Summary

  • 6 commits implementing the marked SDK items from enhancementplan.md. Bumps to v0.5.0.
  • Segment 14.1: estimateEvmGas / estimateSolanaGas / estimateTronGas per chain. TopUpModal renders ≈ $X under the quote. Closes GAP-17.
  • Segment 14.2: opt-in onTelemetry callback on Web3SettleConfig; TelemetryEvent shape { chain, errorCode, walletId, contractVersion, walletDigest, message, phase, timestamp }. EVM/Solana/TRON hooks wire it.
  • Segment 14.5: src/headless/ (3 controllers) + src/wc/ (<web3settle-pay-button> Web Component); both exported via subpaths. Hand-rolled HTMLElement, no Lit dep.
  • Segment 14.6: EIP-712 typed-data signing for EIP-2612 permits — src/evm/permit.ts (detect / sign / build / validate / assertDeadline). usePayment accepts permit?: 'auto' | 'never' | 'require'.
  • Segment 2.2: SDK-level ConfirmationPolicy abstraction so storefronts don't branch on chainId for finality semantics.
  • Tests: 149 / 149 passing (was 141 / 149; jsdom + bad-fixture failures fixed).
  • Bundle: index.js 118.9 kB, solana.js 31.1 kB, tron.js 31.3 kB, headless.js 2.36 kB, wc.js 3.63 kB. No new heavy deps.

Test plan

  • npm run build clean.
  • npm run test -- --run reports 149 / 149 passing.
  • Smoke-test the Web Component via a static HTML page (<script type="module" src="dist/wc.js"> + <web3settle-pay-button>).
  • Verify estimateEvmGas returns a sane USD figure on Base mainnet against a USDC contract.
  • Verify the onTelemetry callback fires on a known-bad payment (insufficient allowance).

Forward-looking note

MerchantPayIn does not yet consume permits on-chain. The SDK ships permit as a primitive ahead of the contract-side payInTokenPermit(...) work; today it eliminates the standalone approve() round-trip but the pay-token tx still goes via payInToken. Tracked separately.

marceloceccon and others added 8 commits May 4, 2026 15:45
In-flight integration of the merchant-api /api/storefronts/{id}/quote
endpoint into the SDK payment modal. Currently not displaying rates
or token selection correctly end-to-end — committing the work-in-
progress state so it can be iterated on.
Restructures the top-up modal around an EVM-focused flow: filters chains
to wagmi-supported set, builds token options inline with stablecoin-first
defaults, and pulls balances via getTokenBalance. Solana/Tron continue to
flow through their dedicated sub-entrypoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per `enhancementplan.md` Segment 2.2, abstract per-chain confirmation /
finality into an SDK-level `ConfirmationPolicy` so storefronts no longer
have to branch on `chainId` to decide "is this safe yet". The SPD-canonical
thresholds (ETH 12, Polygon 30, Base 12, TRON 19, Solana 31) and the
Solana commitment vocabulary live in one place — `core/ConfirmationPolicy.ts`
— and per-chain locked variants under `evm/`, `solana/`, `tron/`
re-export through the existing subpaths. The EVM `usePayment` hook,
Solana `SolanaPaymentPipeline`, and the shared `TransactionStatus` /
`TopUpModal` UI now drive depth + commitment + ETA copy off the policy
instead of inline `chain.confirmations` reads. `ChainConfig.confirmations`
overrides still win when set, so existing consumers keep working with no
change to the public API surface. Adds 32 unit tests in
`src/__tests__/confirmationPolicy.test.ts`.
Closes GAP-17 by giving the SDK a single GasEstimate shape across EVM,
Solana, and TRON so the TopUpModal can render a "≈ $X fee" badge before
the user signs.

  - estimateEvmGas / estimateEvmApproveGas: eth_estimateGas + getGasPrice,
    safety multiplier, optional priceUsd/fetchPriceUsd oracle.
  - estimateSolanaGas: simulateTransaction → unitsConsumed, median priority
    fee from getRecentPrioritizationFees, plus the static 5000-lamport
    signature fee. buildSolanaEstimateInstruction exposed for tests.
  - estimateTronGas: triggerConstantContract → energy_used + raw_data_hex,
    energy*sunPerEnergy + bandwidth, default 280 sun/energy.
  - TopUpModal calls estimateEvmGas in a useEffect and renders the fee
    under the quote — failure hides the badge silently and never blocks
    the pay flow.
  - 19 tests across the three estimators, including the simulate-failure
    fallback paths and the USD oracle integration.

Bundle impact: +~3kB on the EVM index entry; Solana / TRON estimators
land in their respective subpath bundles.
Adds an opt-in onTelemetry callback so merchants get a single
PII-safe TelemetryEvent per failed pay-in. Callback runs through
safeEmit so a buggy analytics handler can never break the flow.

  - core/telemetry: buildTelemetryEvent, redactErrorMessage,
    hashWalletAddress (SubtleCrypto SHA-256 → 16-char hex digest with a
    deterministic FNV-1a fallback for older runtimes), safeEmit.
  - Privacy contract: no plain addresses (only the hash digest), no
    amounts, message redacted (0x-addresses, tx hashes, UUIDs, base58
    pubkeys) and truncated to 240 chars.
  - Web3SettleConfig grows optional onTelemetry + contractVersion fields.
  - Solana + TRON payment hooks: phase tracking + emit on catch.
  - 13 tests covering event shape, redaction, hash determinism,
    callback-throws-safely, and the no-op no-callback path.

EVM hook integration ships in the next commit alongside permit, where
phase tracking is shared between the two paths.
Lets the EVM pay-token flow skip the standalone approve() tx when the
token implements EIP-2612, saving the user one wallet popup and ~$0.50
of gas.

  - evm/permit:
      - detectPermitSupport probes name / version / nonces /
        DOMAIN_SEPARATOR. Never throws — returns { supported: false }
        when any view reverts.
      - buildPermitTypedData and signPermit produce a viem-compatible
        EIP-712 payload, normalise v to {27,28}, and refuse to sign when
        the connected account doesn't match the permit owner.
      - validatePermitSignature: cheap pre-broadcast check (length, r/s
        ranges, EIP-2 high-s rejection, v ∈ {0,1,27,28}).
      - assertDeadlineFresh: throws when the deadline is already past.
  - core/contract: ERC20_PERMIT_ABI + submitPermit() helper that posts
    the on-chain permit(...) tx.
  - hooks/usePayment: new permit?: 'auto' | 'never' | 'require' option
    on startPayment. Default 'auto': probe → sign → submit permit; fall
    back to approve() if the token doesn't support permit. 'require'
    throws on unsupported tokens; 'never' forces the legacy approve()
    path. Also wires the segment-14.2 telemetry phase tracking through
    the EVM hook for parity with Solana / TRON.

The MerchantPayIn contract itself does not consume permits today — this
ships the SDK-side primitive ready for payInTokenPermit(...) once the
on-chain side lands, and it is already useful for merchants who want a
gasless approval before sending tokens to the contract.
Adds two new subpath exports so non-React stacks can drive the SDK:

  - @web3settle/merchant-sdk/headless
      - createPayButtonController: load merchant payment-config and run
        an injected runPayment() routine; subscribe() returns the same
        snapshot shape useState would.
      - createWalletConnectController: connect / disconnect with a
        pluggable connect routine — keeps wagmi / viem / tronweb /
        web3.js out of the headless bundle.
      - createGasEstimateController: wraps any of the three estimators
        from segment 14.1 under a refresh + auto-refresh interval API,
        dispatchable from any UI framework.
  - @web3settle/merchant-sdk/wc
      - <web3settle-pay-button> native HTMLElement (no Lit, no
        framework runtime). attributeChangedCallback rebuilds the
        controller; the button emits payment-started / payment-success /
        payment-error CustomEvents the merchant can listen for.
      - registerWebComponents() is idempotent and runs as a side effect
        on import; gated behind a customElements check so the module
        is safe to require in Node / SSR.

Both subpaths reuse the existing controllers; nothing else in the SDK
imports them, keeping the EVM/Solana/TRON entry bundles unchanged.

vite.config.ts: new entries for headless + wc, both built as ES + CJS.
Bundle: dist/headless.js 2.36 kB, dist/wc.js 3.63 kB (gzip).
Surfaces the new public API from segments 14.1, 14.2, 14.5, 14.6:

  - estimateEvmGas / estimateEvmApproveGas + GasEstimate types
  - detectPermitSupport / signPermit / buildPermitTypedData /
    validatePermitSignature / assertDeadlineFresh + Permit* types
  - buildTelemetryEvent / hashWalletAddress / redactErrorMessage /
    safeEmit + TelemetryEvent / TelemetryCallback / TelemetryChain /
    TelemetryPhase / BuildEventInput

Solana / TRON fee estimators are exported from the existing
@web3settle/merchant-sdk/solana and /tron subpaths via their own
type-only exports off `evm/estimateGas` (kept colocated to keep one
GasEstimate shape across all three chains).

The headless and wc subpaths land in this release; package.json
"exports" map already advertises ./headless and ./wc.

CHANGELOG entry added under [0.5.0] - 2026-05-09.
setTxHash(null);
setError(null);

let phase: TelemetryPhase = 'connect';
setTxHash(null);
setError(null);

let phase: TelemetryPhase = 'connect';
setTxHash(null);
setError(null);

let phase: TelemetryPhase = 'connect';
setTxHash(null);
setError(null);

let phase: TelemetryPhase = 'connect';
- src/components/TopUpModal.tsx: replace `!!`/`as` casts with `Boolean()` and
  locally-typed `account` const; escape apostrophes; drop invalid `role="text"`.
- src/evm/permit.ts: drop redundant `as bigint` and `as Hex` casts now that the
  template literal is annotated `: Hex` directly.
- src/evm/confirmationPolicy.ts, src/core/telemetry.ts: remove stale
  `eslint-disable no-console` directives (warn/error console use is allowed).
- src/headless/usePayButton.ts: branch on apiClient vs apiBaseUrl+storefrontId
  so TS narrows the constructor args without `!`.
- src/hooks/useQuote.ts: explicit null guards before calling fetchQuote.
- src/wc/pay-button.ts: use shadowRoot fallback, swap string compares for
  PaymentStatus enum to satisfy no-unsafe-enum-comparison.
- axios ^1.16.0 (was ^1.15.0): patches GHSA-w9j2-pvgh-6h63 and a chain of
  prototype-pollution / SSRF / CRLF advisories affecting axios <=1.15.1.
  Reaches us via tronweb and @coinbase/cdp-sdk under wagmi/connectors.
- hono ^4.12.18 (was ^4.12.14): patches GHSA-9vqf-7f2p-gf9v plus four
  moderate JSX/JWT/Cache advisories. Reaches us via porto under wagmi/connectors.
- fast-uri ^3.1.2 (new): patches GHSA-q3j6-qgpj-74h6 and GHSA-v39h-62p7-jpjc.
  Reaches us via vite-plugin-dts → @microsoft/api-extractor → ajv (dev only).
- rpc-websockets/uuid ^11.1.1 (new): patches GHSA-w5hq-g745-h8pq.
  Reaches us via @solana/web3.js → rpc-websockets.

`npm audit` now reports 0 vulnerabilities at any level. 149/149 tests still
pass and the bundle builds.
@marceloceccon marceloceccon merged commit 3e640ee into main May 9, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants