@web3settle/merchant-sdk v0.5.0 — gas estimator, telemetry, headless, permit#7
Merged
marceloceccon merged 10 commits intomainfrom May 9, 2026
Merged
@web3settle/merchant-sdk v0.5.0 — gas estimator, telemetry, headless, permit#7marceloceccon merged 10 commits intomainfrom
marceloceccon merged 10 commits intomainfrom
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
enhancementplan.md. Bumps to v0.5.0.estimateEvmGas/estimateSolanaGas/estimateTronGasper chain.TopUpModalrenders≈ $Xunder the quote. Closes GAP-17.onTelemetrycallback onWeb3SettleConfig;TelemetryEventshape{ chain, errorCode, walletId, contractVersion, walletDigest, message, phase, timestamp }. EVM/Solana/TRON hooks wire it.src/headless/(3 controllers) +src/wc/(<web3settle-pay-button>Web Component); both exported via subpaths. Hand-rolled HTMLElement, no Lit dep.src/evm/permit.ts(detect / sign / build / validate / assertDeadline).usePaymentacceptspermit?: 'auto' | 'never' | 'require'.ConfirmationPolicyabstraction so storefronts don't branch on chainId for finality semantics.index.js118.9 kB,solana.js31.1 kB,tron.js31.3 kB,headless.js2.36 kB,wc.js3.63 kB. No new heavy deps.Test plan
npm run buildclean.npm run test -- --runreports 149 / 149 passing.<script type="module" src="dist/wc.js">+<web3settle-pay-button>).estimateEvmGasreturns a sane USD figure on Base mainnet against a USDC contract.onTelemetrycallback fires on a known-bad payment (insufficient allowance).Forward-looking note
MerchantPayIndoes not yet consume permits on-chain. The SDK ships permit as a primitive ahead of the contract-sidepayInTokenPermit(...)work; today it eliminates the standaloneapprove()round-trip but the pay-token tx still goes viapayInToken. Tracked separately.