diff --git a/components/HistoryRow.tsx b/components/HistoryRow.tsx index cf3752b..19103b7 100644 --- a/components/HistoryRow.tsx +++ b/components/HistoryRow.tsx @@ -145,7 +145,19 @@ export function HistoryRow({
{entry.chainSlug}
-
{t('columnFee')}
+ {/* alpha 中は OpenPay 利用手数料 0% のため、gasless で feeAmount>0 のときは + それは運営が立替えた gas の実費回収 (ネットワーク手数料相当・JPYC sponsorship) + であり利用手数料ではない。よってその場合は「ネットワーク手数料」と表示する。 + (恒久対応: feeAmount に service fee と gas reimbursement を混在させず分離記録する。 + 将来 service fee>0 にする際は必須 — 下記 CSV/stats も同根。) */} +
+ {entry.payMode === 'gasless' && + entry.feeAmount != null && + entry.feeAmount !== '0' && + entry.feeAmount !== '' + ? t('columnNetworkFee') + : t('columnFee')} +
{fmt(entry.feeAmount, entry.asset)}
{entry.provider === 'circle' && diff --git a/hooks/useSmartAccount.ts b/hooks/useSmartAccount.ts index af435a5..bf2a61c 100644 --- a/hooks/useSmartAccount.ts +++ b/hooks/useSmartAccount.ts @@ -191,6 +191,24 @@ export function useSmartAccount( chainId, symbol: deployment.symbol, }); + // ⚠️ MetaMask Smart Account (Stateless7702) ガスレスは現在 viem ↔ + // @metamask/delegation-toolkit の非互換で全件失敗する: Stateless7702 は + // account.address === 署名 EOA で、viem 2.50 の ERC-7739 ガードが「外部署名で + // verifyingContract に同一(internal)アドレスは不可」と弾く ("External signature + // requests cannot use internal accounts as the verifying contract")。toolkit 0.13.0 + // が最新で upstream 修正待ち。既定 OFF で standard mode に倒し、生エラーを出さない。 + // upstream 互換修正 + 実機再検証後に NEXT_PUBLIC_ENABLE_METAMASK_SMART_ACCOUNT で再有効化。 + if (!env.enableMetaMaskSmartAccount) { + logger.warn('smart_account.metamask_gasless_disabled', { + delegateAddress: detection.delegateAddress, + chainId, + symbol: deployment.symbol, + }); + throw new IncompatibleSmartAccountError({ + delegateAddress: detection.delegateAddress, + i18nKey: 'errorMetaMaskGaslessUnavailable', + }); + } // @metamask/delegation-toolkit は ~30 kB gzip。MetaMask Smart Account // ユーザ向けにのみ必要なので dynamic import で lazy load。 const { buildMetaMaskSmartAccountClient } = await import( diff --git a/lib/accountDetection.ts b/lib/accountDetection.ts index 1ef5497..f761265 100644 --- a/lib/accountDetection.ts +++ b/lib/accountDetection.ts @@ -83,7 +83,10 @@ export type IncompatibleSmartAccountI18nKey = // canonical EIP-7702 非対応 chain (Avalanche C-Chain = ACP-209「7702 style」AA)。 // 委任済み EOA でも標準 bundler 経由の 7702 UserOp が AA23 で revert するため // standard mode 案内に倒す ([[chainSupportsCanonical7702]])。 - | 'errorChainNo7702'; + | 'errorChainNo7702' + // MetaMask Smart Account (Stateless7702) ガスレスが viem 2.50 ERC-7739 ガードと + // delegation-toolkit 0.13.0 の非互換で署名失敗するため一時無効化 (standard 案内に倒す)。 + | 'errorMetaMaskGaslessUnavailable'; export class IncompatibleSmartAccountError extends Error { readonly delegateAddress: Address | null; diff --git a/lib/env.ts b/lib/env.ts index 3b9c90b..169d129 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -292,6 +292,13 @@ export const env = { enableMav2: process.env.NEXT_PUBLIC_ENABLE_MAV2 === '1' || process.env.NEXT_PUBLIC_ENABLE_MAV2 === 'true', + // MetaMask Smart Account (Stateless7702) ガスレス経路の有効化フラグ。**既定 OFF**。 + // 現在 viem 2.50 の ERC-7739 same-address ガードと delegation-toolkit 0.13.0 の + // 非互換で署名が全件失敗するため無効化し standard mode に倒している + // (hooks/useSmartAccount.ts)。upstream 互換修正 + 実機再検証後に '1'/'true' で再有効化。 + enableMetaMaskSmartAccount: + process.env.NEXT_PUBLIC_ENABLE_METAMASK_SMART_ACCOUNT === '1' || + process.env.NEXT_PUBLIC_ENABLE_METAMASK_SMART_ACCOUNT === 'true', // Circle Paymaster (USDC ガスレスを Pimlico erc20 → Circle 公式 paymaster に // 切替える) の有効化フラグ。既定 OFF で投入し testnet 検証 → mainnet 1 chain // (Base) と段階展開する。OFF の間は USDC ガスレスは従来通り Pimlico erc20。 diff --git a/messages/en.json b/messages/en.json index 28598ad..0044af9 100644 --- a/messages/en.json +++ b/messages/en.json @@ -307,8 +307,9 @@ "errorMav2Disabled": "Payments from wallets backed by Alchemy Modular Account v2 (such as HashPort) are being rolled out gradually. Please try again later.", "errorMav2KaiaPolygon": "Payments on Kaia are not supported for wallets delegated to Alchemy Modular Account v2. Please use a Polygon / Base / Arbitrum / Optimism payment QR, or try a different wallet.", "errorMetaMaskKaia": "MetaMask Smart Account is not supported on Kaia yet (MetaMask has not deployed its delegator on Kaia). Toggle off \"Smart account\" in MetaMask Account details to revert to a plain EOA, or try a different wallet. MetaMask Smart Account works for gasless payments on Polygon / Base / Arbitrum / Optimism / Ethereum.", - "errorPristineNoBootstrap": "This wallet isn't set up for gasless payments yet. You can pay now in standard mode (you pay network gas). If you use MetaMask, turn on \"Smart account\" in Account details to enable gasless next time.", + "errorPristineNoBootstrap": "This wallet isn't set up for gasless payments yet. You can pay now in standard mode (you pay network gas).", "errorChainNo7702": "Gasless USDC payments aren't available on this chain yet (its EIP-7702 implementation differs from the standard). You can pay now in standard mode (you pay network gas).", + "errorMetaMaskGaslessUnavailable": "Gasless payments via MetaMask Smart Account are temporarily unavailable (a wallet-side compatibility issue). You can pay now in standard mode (you pay network gas).", "successTitle": "Payment complete", "successUserOp": "UserOp Hash", "successTx": "Tx Hash", @@ -363,8 +364,9 @@ "errorMav2Disabled": "Tip sending from wallets backed by Alchemy Modular Account v2 (such as HashPort) is being rolled out gradually. Please try again later.", "errorMav2KaiaPolygon": "Sending tips on Kaia is not supported for wallets delegated to Alchemy Modular Account v2. Please use a Polygon / Base / Arbitrum / Optimism Tip link, or try a different wallet.", "errorMetaMaskKaia": "MetaMask Smart Account is not supported on Kaia yet. Toggle off \"Smart account\" in MetaMask Account details, or try a different wallet.", - "errorPristineNoBootstrap": "This wallet isn't set up for gasless payments yet, and tips require gasless mode. If you use MetaMask, turn on \"Smart account\" in Account details and reconnect, or try a wallet that's already set up for gasless.", + "errorPristineNoBootstrap": "This wallet isn't set up for gasless payments yet, and tips require gasless mode. Please try a wallet that's already set up for gasless.", "errorChainNo7702": "Gasless isn't available on this chain yet (its EIP-7702 implementation differs from the standard), and tips require gasless mode. Please use another chain such as Polygon / Base / Arbitrum / Optimism.", + "errorMetaMaskGaslessUnavailable": "Gasless payments via MetaMask Smart Account are temporarily unavailable (a wallet-side compatibility issue). Tips require gasless mode — please try a different wallet that's already set up for gasless.", "successTitle": "Tip sent!", "successUserOp": "UserOp", "successTx": "Tx", @@ -478,8 +480,9 @@ "errorMav2Disabled": "Payments from wallets backed by Alchemy Modular Account v2 (such as HashPort) are being rolled out gradually. Please try again later.", "errorMav2KaiaPolygon": "Checkout on Kaia is not supported for wallets delegated to Alchemy Modular Account v2. Please use a Polygon / Base / Arbitrum / Optimism checkout URL, or try a different wallet.", "errorMetaMaskKaia": "MetaMask Smart Account is not supported on Kaia yet. Toggle off \"Smart account\" in MetaMask Account details, or try a different wallet.", - "errorPristineNoBootstrap": "This wallet isn't set up for gasless payments yet. You can pay now in standard mode (you pay network gas). If you use MetaMask, turn on \"Smart account\" in Account details to enable gasless next time.", + "errorPristineNoBootstrap": "This wallet isn't set up for gasless payments yet. You can pay now in standard mode (you pay network gas).", "errorChainNo7702": "Gasless USDC payments aren't available on this chain yet (its EIP-7702 implementation differs from the standard). You can pay now in standard mode (you pay network gas).", + "errorMetaMaskGaslessUnavailable": "Gasless payments via MetaMask Smart Account are temporarily unavailable (a wallet-side compatibility issue). You can pay now in standard mode (you pay network gas).", "successTitle": "Payment complete", "successBody": "Thanks for your purchase. The transaction is confirmed on-chain.", "successUserOp": "UserOp Hash", @@ -713,6 +716,7 @@ "columnAsset": "Asset", "columnAmount": "Amount", "columnFee": "OpenPay fee", + "columnNetworkFee": "Network fee", "columnNetwork": "Network", "columnMode": "Mode", "columnGasMode": "Gas", @@ -939,7 +943,7 @@ "tipOnlyTitle": "Unsupported Smart Account detected", "tipOnlyBody": "Your wallet is delegated to a Smart Account that OpenPay cannot use for gasless payments (delegate: {address}). Tips require gasless mode, so this wallet cannot send tips at this time. If you are using MetaMask, toggle off \"Smart account\" in Account details. Or try a different wallet.", "pristineTitle": "Gasless not available for this wallet yet", - "pristineBannerBody": "This wallet isn't set up for gasless payments yet. Switch to standard mode to pay now — you pay gas in {nativeToken}. If you use MetaMask, turn on \"Smart account\" in Account details to enable gasless next time.", - "pristineTipBody": "This wallet isn't set up for gasless payments yet, and tips require gasless mode. If you use MetaMask, turn on \"Smart account\" in Account details and reconnect, or try a wallet that's already set up for gasless." + "pristineBannerBody": "This wallet isn't set up for gasless payments yet. Switch to standard mode to pay now — you pay gas in {nativeToken}.", + "pristineTipBody": "This wallet isn't set up for gasless payments yet, and tips require gasless mode. Please try a wallet that's already set up for gasless." } } diff --git a/messages/ja.json b/messages/ja.json index ba2042c..51761fe 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -307,8 +307,9 @@ "errorMav2Disabled": "現在 HashPort 等の一部ウォレット (Alchemy Modular Account v2) での決済は段階公開中です。少々お待ちください。", "errorMav2KaiaPolygon": "Kaia チェーン上では Alchemy Modular Account v2 に委任されたウォレットでは決済できません。お手数ですが Polygon / Base / Arbitrum / Optimism 等の他チェーン版 QR をご利用いただくか、別のウォレットでお試しください。", "errorMetaMaskKaia": "Kaia チェーン上では MetaMask Smart Account には現在対応していません (MetaMask は Kaia への delegator を未 deploy)。MetaMask の場合は「アカウント詳細 → Smart account」をオフにして元の EOA に戻すか、別のウォレットでお試しください。Polygon / Base / Arbitrum / Optimism / Ethereum チェーンでは MetaMask Smart Account で gasless 決済可能です。", - "errorPristineNoBootstrap": "このウォレットはまだガスレス決済の準備ができていません。通常決済 (ガス代自己負担) で今すぐ支払えます。MetaMask の場合は「アカウント詳細 → Smart account」をオンにすると次回からガスレスになります。", + "errorPristineNoBootstrap": "このウォレットはまだガスレス決済の準備ができていません。通常決済 (ガス代自己負担) で今すぐ支払えます。", "errorChainNo7702": "このチェーンでは現在 USDC ガスレス決済に対応していません (このチェーンの EIP-7702 実装が標準と異なるため)。通常決済 (ガス代自己負担) で今すぐ支払えます。", + "errorMetaMaskGaslessUnavailable": "MetaMask Smart Account でのガスレス決済は現在ご利用いただけません (ウォレット側の互換性問題のため一時停止中)。通常決済 (ガス代自己負担) で今すぐ支払えます。", "successTitle": "決済が完了しました", "successUserOp": "UserOp Hash", "successTx": "Tx Hash", @@ -363,8 +364,9 @@ "errorMav2Disabled": "現在 HashPort 等の一部ウォレット (Alchemy Modular Account v2) でのチップ送信は段階公開中です。少々お待ちください。", "errorMav2KaiaPolygon": "Kaia チェーン上では Alchemy Modular Account v2 に委任されたウォレットからチップを送れません。お手数ですが Polygon / Base / Arbitrum / Optimism 等の他チェーン版 Tip リンクをご利用いただくか、別のウォレットでお試しください。", "errorMetaMaskKaia": "Kaia チェーン上では MetaMask Smart Account には現在対応していません。MetaMask の「アカウント詳細 → Smart account」をオフにするか、別のウォレットでお試しください。", - "errorPristineNoBootstrap": "このウォレットはまだガスレス決済の準備ができていません。チップにはガスレスモードが必要です。MetaMask の「アカウント詳細 → Smart account」をオンにして再接続するか、ガスレス対応済みのウォレットでお試しください。", + "errorPristineNoBootstrap": "このウォレットはまだガスレス決済の準備ができていません。チップにはガスレスモードが必要です。ガスレス対応済みのウォレットでお試しください。", "errorChainNo7702": "このチェーンでは現在 USDC ガスレスに対応していないため、チップを送れません (このチェーンの EIP-7702 実装が標準と異なるため)。Polygon / Base / Arbitrum / Optimism 等の他チェーンをご利用ください。", + "errorMetaMaskGaslessUnavailable": "MetaMask Smart Account でのガスレス決済は現在ご利用いただけません (ウォレット側の互換性問題のため一時停止中)。チップにはガスレスが必要なため、ガスレス対応済みの別ウォレットでお試しください。", "successTitle": "チップを送信しました", "successUserOp": "UserOp", "successTx": "Tx", @@ -478,8 +480,9 @@ "errorMav2Disabled": "現在 HashPort 等の一部ウォレット (Alchemy Modular Account v2) での決済は段階公開中です。少々お待ちください。", "errorMav2KaiaPolygon": "Kaia チェーン上では Alchemy Modular Account v2 に委任されたウォレットでチェックアウトできません。お手数ですが Polygon / Base / Arbitrum / Optimism 等の他チェーン版のチェックアウト URL をご利用いただくか、別のウォレットでお試しください。", "errorMetaMaskKaia": "Kaia チェーン上では MetaMask Smart Account には現在対応していません。MetaMask の「アカウント詳細 → Smart account」をオフにするか、別のウォレットでお試しください。", - "errorPristineNoBootstrap": "このウォレットはまだガスレス決済の準備ができていません。通常決済 (ガス代自己負担) で今すぐ支払えます。MetaMask の場合は「アカウント詳細 → Smart account」をオンにすると次回からガスレスになります。", + "errorPristineNoBootstrap": "このウォレットはまだガスレス決済の準備ができていません。通常決済 (ガス代自己負担) で今すぐ支払えます。", "errorChainNo7702": "このチェーンでは現在 USDC ガスレス決済に対応していません (このチェーンの EIP-7702 実装が標準と異なるため)。通常決済 (ガス代自己負担) で今すぐ支払えます。", + "errorMetaMaskGaslessUnavailable": "MetaMask Smart Account でのガスレス決済は現在ご利用いただけません (ウォレット側の互換性問題のため一時停止中)。通常決済 (ガス代自己負担) で今すぐ支払えます。", "successTitle": "お支払いが完了しました", "successBody": "ご注文ありがとうございます。決済はオンチェーンで確定しました。", "successUserOp": "UserOp Hash", @@ -713,6 +716,7 @@ "columnAsset": "通貨", "columnAmount": "金額", "columnFee": "OpenPay 利用手数料", + "columnNetworkFee": "ネットワーク手数料", "columnNetwork": "ネットワーク", "columnMode": "決済モード", "columnGasMode": "ガス負担", @@ -939,7 +943,7 @@ "tipOnlyTitle": "未対応の Smart Account を検出", "tipOnlyBody": "お使いのウォレットは OpenPay のガスレス決済に対応していない Smart Account に委任されています (delegate: {address})。チップ送信にはガスレスモードが必要なため、このウォレットではチップを送れません。MetaMask の場合は「アカウント詳細 → Smart account」をオフにするか、別のウォレットでお試しください。", "pristineTitle": "このウォレットはまだガスレス決済の準備ができていません", - "pristineBannerBody": "このウォレットはまだガスレス決済の準備ができていません。通常決済モードに切り替えると今すぐ支払えます — ガス代は {nativeToken} で自己負担です。MetaMask の場合は「アカウント詳細 → Smart account」をオンにすると次回からガスレスになります。", - "pristineTipBody": "このウォレットはまだガスレス決済の準備ができていません。チップ送信にはガスレスモードが必要です。MetaMask の場合は「アカウント詳細 → Smart account」をオンにして再接続するか、ガスレス対応済みのウォレットでお試しください。" + "pristineBannerBody": "このウォレットはまだガスレス決済の準備ができていません。通常決済モードに切り替えると今すぐ支払えます — ガス代は {nativeToken} で自己負担です。", + "pristineTipBody": "このウォレットはまだガスレス決済の準備ができていません。チップ送信にはガスレスモードが必要です。ガスレス対応済みのウォレットでお試しください。" } } diff --git a/tests/components/HistoryRow.test.tsx b/tests/components/HistoryRow.test.tsx index df949c0..1d9ca75 100644 --- a/tests/components/HistoryRow.test.tsx +++ b/tests/components/HistoryRow.test.tsx @@ -133,6 +133,28 @@ describe('HistoryRow', () => { expect(screen.getByText(/通常決済 \(ガスあり\) · —/)).toBeInTheDocument(); }); + it('gasless で feeAmount>0 は「ネットワーク手数料」表示 (alpha は利用手数料 0% なので gas 実費回収)', () => { + render( + undefined} + />, + ); + expect(screen.getByText('ネットワーク手数料')).toBeInTheDocument(); + expect(screen.queryByText('OpenPay 利用手数料')).toBeNull(); + }); + + it('standard / feeAmount=0 は「OpenPay 利用手数料」表示 (混同しない)', () => { + render( + undefined} + />, + ); + expect(screen.getByText('OpenPay 利用手数料')).toBeInTheDocument(); + expect(screen.queryByText('ネットワーク手数料')).toBeNull(); + }); + it('削除 button → confirm true で onRemove(id) が呼ばれる', async () => { const user = userEvent.setup(); const onRemove = vi.fn(); diff --git a/tests/lib/i18nKeys.test.ts b/tests/lib/i18nKeys.test.ts index 52c0fa2..dda2867 100644 --- a/tests/lib/i18nKeys.test.ts +++ b/tests/lib/i18nKeys.test.ts @@ -37,6 +37,9 @@ describe('i18n: smart account 互換性エラー (3 form 名前空間 × ja/en)' // canonical EIP-7702 非対応 chain (Avalanche = ACP-209「7702 style」AA) で // 委任済み EOA を standard mode に倒す (chainSupportsCanonical7702 ガード) 'errorChainNo7702', + // MetaMask Smart Account ガスレスが viem 2.50 ERC-7739 非互換で一時無効 + // (standard mode 案内に倒す・useSmartAccount の metamask-7702 ガード) + 'errorMetaMaskGaslessUnavailable', ] as const; for (const ns of FORM_NAMESPACES) { for (const key of SA_KEYS) {