diff --git a/src/app/features/fee-editor/bitcoin/bitcoin-fee-editor.provider.tsx b/src/app/features/fee-editor/bitcoin/bitcoin-fee-editor.provider.tsx
index e4ab9abcf7a..e5f6e462790 100644
--- a/src/app/features/fee-editor/bitcoin/bitcoin-fee-editor.provider.tsx
+++ b/src/app/features/fee-editor/bitcoin/bitcoin-fee-editor.provider.tsx
@@ -42,6 +42,7 @@ export function BitcoinFeeEditorProvider({
feeType="fee-rate"
getCustomFee={getCustomFee}
isLoadingFees={isLoading}
+ isSponsored={false}
marketData={marketData}
onGoBack={onGoBack}
>
diff --git a/src/app/features/fee-editor/components/selected-fee-item.tsx b/src/app/features/fee-editor/components/selected-fee-item.tsx
index 9528dd31dd7..b17aa4c0653 100644
--- a/src/app/features/fee-editor/components/selected-fee-item.tsx
+++ b/src/app/features/fee-editor/components/selected-fee-item.tsx
@@ -1,5 +1,7 @@
+import { Flex, HStack, styled } from 'leather-styles/jsx';
+
import type { MarketData } from '@leather.io/models';
-import { Approver, Pressable } from '@leather.io/ui';
+import { Approver, Avatar, Badge, Flag, PlaceholderIcon, Pressable } from '@leather.io/ui';
import { CryptoAssetItemPlaceholder } from '@app/components/crypto-asset-item/crypto-asset-item-placeholder';
import type { Fee, FeeType } from '@app/features/fee-editor/fee-editor.context';
@@ -10,6 +12,7 @@ import { FeeValueItemLayout } from './fee-value-item.layout';
interface SelectedFeeItemProps {
feeType: FeeType;
isLoading: boolean;
+ isSponsored: boolean;
marketData: MarketData;
onEditFee(): void;
selectedFee: Fee | null;
@@ -17,15 +20,29 @@ interface SelectedFeeItemProps {
export function SelectedFeeItem({
feeType,
isLoading,
+ isSponsored,
marketData,
onEditFee,
selectedFee,
}: SelectedFeeItemProps) {
+ if (isSponsored)
+ return (
+
+ } />} my="space.02">
+
+ Sponsored fee
+
+
+
+
+
+
+ );
+
if (isLoading || !selectedFee)
return (
- Fee
-
+
);
diff --git a/src/app/features/fee-editor/fee-editor.context.ts b/src/app/features/fee-editor/fee-editor.context.ts
index 8c0b71717cb..cce28060032 100644
--- a/src/app/features/fee-editor/fee-editor.context.ts
+++ b/src/app/features/fee-editor/fee-editor.context.ts
@@ -21,6 +21,7 @@ interface FeeEditorContext {
feeType: FeeType;
loadedFee: Fee;
isLoadingFees: boolean;
+ isSponsored: boolean;
marketData: MarketData;
fees: Fees;
selectedFee: Fee;
diff --git a/src/app/features/fee-editor/fee-editor.provider.tsx b/src/app/features/fee-editor/fee-editor.provider.tsx
index e998150e5ef..4fb6baa8a98 100644
--- a/src/app/features/fee-editor/fee-editor.provider.tsx
+++ b/src/app/features/fee-editor/fee-editor.provider.tsx
@@ -20,6 +20,7 @@ interface FeeEditorProviderProps extends HasChildren {
feeType: FeeType;
getCustomFee(rate: number): Fee;
isLoadingFees: boolean;
+ isSponsored: boolean;
marketData: MarketData;
onGoBack(): void;
}
@@ -30,6 +31,7 @@ export function FeeEditorProvider({
feeType,
getCustomFee,
isLoadingFees,
+ isSponsored,
marketData,
onGoBack,
}: FeeEditorProviderProps) {
@@ -45,6 +47,7 @@ export function FeeEditorProvider({
- {({ fees, isLoading, getCustomFee }) => {
+ {({ fees, isLoading, isSponsored, getCustomFee }) => {
return (
diff --git a/src/app/features/fee-editor/stacks/stacks-fees-loader.tsx b/src/app/features/fee-editor/stacks/stacks-fees-loader.tsx
index ef563d21291..b4624557d1f 100644
--- a/src/app/features/fee-editor/stacks/stacks-fees-loader.tsx
+++ b/src/app/features/fee-editor/stacks/stacks-fees-loader.tsx
@@ -1,7 +1,8 @@
-import type { StacksTransactionWire } from '@stacks/transactions';
+import { AuthType, type StacksTransactionWire } from '@stacks/transactions';
import { createMoneyFromDecimal } from '@leather.io/utils';
+import { useCheckSbtcSponsorshipEligible } from '@app/query/sbtc/sponsored-transactions.hooks';
import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks';
import { type Fee, type Fees } from '../fee-editor.context';
@@ -10,16 +11,19 @@ import { useStacksFees } from './use-stacks-fees';
interface StacksFees {
fees: Fees;
isLoading: boolean;
+ isSponsored: boolean;
getCustomFee(value: number): Fee;
}
interface StacksFeesLoaderProps {
- children({ fees, isLoading, getCustomFee }: StacksFees): React.JSX.Element;
+ children({ fees, isLoading, isSponsored, getCustomFee }: StacksFees): React.JSX.Element;
unsignedTx: StacksTransactionWire;
}
export function StacksFeesLoader({ children, unsignedTx }: StacksFeesLoaderProps) {
- const { data: stxFees, isLoading } = useCalculateStacksTxFees(unsignedTx);
+ const { data: stxFees, isLoading: isLoadingFees } = useCalculateStacksTxFees(unsignedTx);
const fees = useStacksFees({ fees: stxFees });
+ const { isVerifying: isVerifyingSbtcSponsorship, result: sbtcSponsorshipEligibility } =
+ useCheckSbtcSponsorshipEligible({ baseTx: { transaction: unsignedTx }, stxFees });
function getCustomFee(feeValue: number): Fee {
return {
@@ -31,5 +35,11 @@ export function StacksFeesLoader({ children, unsignedTx }: StacksFeesLoaderProps
}
if (!fees) return null;
- return children({ fees, isLoading, getCustomFee });
+ return children({
+ fees,
+ isLoading: isLoadingFees || isVerifyingSbtcSponsorship,
+ isSponsored:
+ sbtcSponsorshipEligibility?.isEligible || unsignedTx.auth.authType === AuthType.Sponsored,
+ getCustomFee,
+ });
}
diff --git a/src/app/features/nonce-editor/nonce-item.tsx b/src/app/features/nonce-editor/nonce-item.tsx
index c2985635ffd..754baa10c59 100644
--- a/src/app/features/nonce-editor/nonce-item.tsx
+++ b/src/app/features/nonce-editor/nonce-item.tsx
@@ -11,11 +11,11 @@ import {
import type { Nonce } from './nonce-editor.context';
-interface SelectedFeeItemProps {
+interface NonceItemProps {
nonce: Nonce;
onEditNonce(): void;
}
-export function NonceItem({ nonce, onEditNonce }: SelectedFeeItemProps) {
+export function NonceItem({ nonce, onEditNonce }: NonceItemProps) {
return (
diff --git a/src/app/features/rpc-transaction-request/stacks/use-sign-and-broadcast-stacks-transaction.ts b/src/app/features/rpc-transaction-request/stacks/use-sign-and-broadcast-stacks-transaction.ts
index d05d5f1b0dd..2b0fa91e0fc 100644
--- a/src/app/features/rpc-transaction-request/stacks/use-sign-and-broadcast-stacks-transaction.ts
+++ b/src/app/features/rpc-transaction-request/stacks/use-sign-and-broadcast-stacks-transaction.ts
@@ -1,7 +1,11 @@
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
-import type { StacksTransactionWire, TxBroadcastResultRejected } from '@stacks/transactions';
+import {
+ AuthType,
+ type StacksTransactionWire,
+ type TxBroadcastResultRejected,
+} from '@stacks/transactions';
import {
RpcErrorCode,
@@ -50,6 +54,23 @@ export function useSignAndBroadcastStacksTransaction(method: RpcMethodNames) {
throw new Error('Error signing stacks transaction');
}
+ // If the transaction is sponsored, we do not broadcast it
+ const isSponsored = signedTx.auth?.authType === AuthType.Sponsored;
+ if (isSponsored) {
+ chrome.tabs.sendMessage(
+ tabId,
+ createRpcSuccessResponse(method, {
+ id: requestId,
+ result: {
+ txid: '',
+ transaction: signedTx.serialize(),
+ },
+ })
+ );
+ await delay(500);
+ closeWindow();
+ }
+
function onError(error: Error | string, reason?: TxBroadcastResultRejected['reason']) {
const message = isString(error) ? error : error.message;
if (reason) toast.error(getErrorMessage(reason));
diff --git a/src/app/features/rpc-transaction-request/transaction-actions/transaction-actions-with-spend.tsx b/src/app/features/rpc-transaction-request/transaction-actions/transaction-actions-with-spend.tsx
index 01388df3fed..250a756f6b3 100644
--- a/src/app/features/rpc-transaction-request/transaction-actions/transaction-actions-with-spend.tsx
+++ b/src/app/features/rpc-transaction-request/transaction-actions/transaction-actions-with-spend.tsx
@@ -15,11 +15,13 @@ import { useRpcTransactionRequest } from '../use-rpc-transaction-request';
interface TransactionActionsWithSpendProps {
isLoading: boolean;
+ isSponsored: boolean;
txAmount: Money;
onApprove(): Promise;
}
export function TransactionActionsWithSpend({
isLoading,
+ isSponsored,
txAmount,
onApprove,
}: TransactionActionsWithSpendProps) {
@@ -33,7 +35,8 @@ export function TransactionActionsWithSpend({
}, [marketData, selectedFee?.txFee, txAmount]);
// TODO LEA-2537: Refactor error state
- const isInsufficientBalance = availableBalance.amount.isLessThan(totalSpend.amount);
+ const isInsufficientBalance =
+ !isSponsored && availableBalance.amount.isLessThan(totalSpend.amount);
return (
(() => {
if (!origin) return TransactionErrorReason.ExpiredRequest;
diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx
index 8289bedf77d..3837d566835 100644
--- a/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx
+++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx
@@ -78,6 +78,7 @@ export function RpcSendTransfer() {
getUnsignedStacksContractCallOptions({
- fee: selectedFee.txFee,
+ fee: isSponsored ? createMoney(0, 'STX') : selectedFee.txFee,
network,
nonce,
publicKey,
}),
- [network, nonce, publicKey, selectedFee.txFee]
+ [isSponsored, network, nonce, publicKey, selectedFee.txFee]
);
async function onApproveTransaction() {
@@ -58,6 +64,7 @@ export function RpcStxCallContract() {
actions={
@@ -82,6 +90,7 @@ export function RpcStxDeployContract() {
@@ -88,6 +96,7 @@ export function RpcStxTransferSip10Ft() {
@@ -84,6 +92,7 @@ export function RpcStxTransferSip9Nft() {
@@ -80,6 +88,7 @@ export function RpcStxTransferStx() {
();
const [lastAddressChecked, setLastAddressChecked] = useState();
diff --git a/tests/specs/rpc-stx-call-contract/rpc-stx-call-contract.spec.ts b/tests/specs/rpc-sip30-requests/rpc-stx-call-contract.spec.ts
similarity index 100%
rename from tests/specs/rpc-stx-call-contract/rpc-stx-call-contract.spec.ts
rename to tests/specs/rpc-sip30-requests/rpc-stx-call-contract.spec.ts
diff --git a/tests/specs/rpc-stx-deploy-contract/rpc-stx-deploy-contract.spec.ts b/tests/specs/rpc-sip30-requests/rpc-stx-deploy-contract.spec.ts
similarity index 100%
rename from tests/specs/rpc-stx-deploy-contract/rpc-stx-deploy-contract.spec.ts
rename to tests/specs/rpc-sip30-requests/rpc-stx-deploy-contract.spec.ts
diff --git a/tests/specs/rpc-stx-message-signing/rpc-stx-message-signing.spec.ts b/tests/specs/rpc-sip30-requests/rpc-stx-message-signing.spec.ts
similarity index 100%
rename from tests/specs/rpc-stx-message-signing/rpc-stx-message-signing.spec.ts
rename to tests/specs/rpc-sip30-requests/rpc-stx-message-signing.spec.ts
diff --git a/tests/specs/rpc-stx-sign-transaction/rpc-stx-sign-transaction.spec.ts b/tests/specs/rpc-sip30-requests/rpc-stx-sign-transaction.spec.ts
similarity index 100%
rename from tests/specs/rpc-stx-sign-transaction/rpc-stx-sign-transaction.spec.ts
rename to tests/specs/rpc-sip30-requests/rpc-stx-sign-transaction.spec.ts
diff --git a/tests/specs/rpc-stx-transfer-sip10-ft/rpc-stx-transfer-sip10-ft.spec.ts b/tests/specs/rpc-sip30-requests/rpc-stx-transfer-sip10-ft.spec.ts
similarity index 100%
rename from tests/specs/rpc-stx-transfer-sip10-ft/rpc-stx-transfer-sip10-ft.spec.ts
rename to tests/specs/rpc-sip30-requests/rpc-stx-transfer-sip10-ft.spec.ts
diff --git a/tests/specs/rpc-stx-transfer-sip9-nft/rpc-stx-transfer-sip9-nft.spec.ts b/tests/specs/rpc-sip30-requests/rpc-stx-transfer-sip9-nft.spec.ts
similarity index 100%
rename from tests/specs/rpc-stx-transfer-sip9-nft/rpc-stx-transfer-sip9-nft.spec.ts
rename to tests/specs/rpc-sip30-requests/rpc-stx-transfer-sip9-nft.spec.ts
diff --git a/tests/specs/rpc-stx-transfer-stx/rpc-stx-transfer-stx.spec.tsx b/tests/specs/rpc-sip30-requests/rpc-stx-transfer-stx.spec.tsx
similarity index 100%
rename from tests/specs/rpc-stx-transfer-stx/rpc-stx-transfer-stx.spec.tsx
rename to tests/specs/rpc-sip30-requests/rpc-stx-transfer-stx.spec.tsx