@b3dotfun/sdk 0.1.70-alpha.14 → 0.1.70-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -1
- package/dist/cjs/anyspend/react/components/AnySpend.js +92 -27
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -1
- package/dist/cjs/anyspend/react/components/common/PureTransferView.d.ts +30 -0
- package/dist/cjs/anyspend/react/components/common/PureTransferView.js +64 -0
- package/dist/cjs/global-account/client-manager.js +32 -0
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
- package/dist/esm/anyspend/react/components/AnySpend.js +92 -27
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +4 -1
- package/dist/esm/anyspend/react/components/common/PureTransferView.d.ts +30 -0
- package/dist/esm/anyspend/react/components/common/PureTransferView.js +61 -0
- package/dist/esm/global-account/client-manager.js +32 -0
- package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
- package/dist/types/anyspend/react/components/common/PureTransferView.d.ts +30 -0
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +119 -29
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +5 -1
- package/src/anyspend/react/components/common/PureTransferView.tsx +212 -0
- package/src/global-account/client-manager.ts +33 -0
|
@@ -71,6 +71,7 @@ import { LoginStep } from "@b3dotfun/sdk/global-account/react/components/SignInW
|
|
|
71
71
|
import { PanelOnramp } from "./common/PanelOnramp";
|
|
72
72
|
import { PanelOnrampPayment } from "./common/PanelOnrampPayment";
|
|
73
73
|
import { PointsDetailPanel } from "./common/PointsDetailPanel";
|
|
74
|
+
import { PureTransferView } from "./common/PureTransferView";
|
|
74
75
|
import { RecipientSelection } from "./common/RecipientSelection";
|
|
75
76
|
import { TabSection } from "./common/TabSection";
|
|
76
77
|
import { AnySpendCustomizationProvider, useAnySpendCustomization } from "./context/AnySpendCustomizationContext";
|
|
@@ -100,6 +101,7 @@ export enum PanelView {
|
|
|
100
101
|
DIRECT_TRANSFER_SUCCESS,
|
|
101
102
|
FIAT_KYC,
|
|
102
103
|
FIAT_AUTH,
|
|
104
|
+
PURE_TRANSFER_ADDRESS,
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
const ANYSPEND_RECIPIENTS_KEY = "anyspend_recipients";
|
|
@@ -687,7 +689,10 @@ function AnySpendInner({
|
|
|
687
689
|
globalAddress,
|
|
688
690
|
});
|
|
689
691
|
|
|
690
|
-
const recipientProfile = useProfile({
|
|
692
|
+
const recipientProfile = useProfile({
|
|
693
|
+
address: effectiveRecipientAddress,
|
|
694
|
+
fresh: true,
|
|
695
|
+
});
|
|
691
696
|
const recipientName = recipientProfile.data?.name;
|
|
692
697
|
|
|
693
698
|
// Check token balance for crypto payments
|
|
@@ -807,12 +812,15 @@ function AnySpendInner({
|
|
|
807
812
|
anyspendQuote.data.currencyOut?.currency?.decimals
|
|
808
813
|
) {
|
|
809
814
|
if (isSrcInputDirty) {
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
815
|
+
// The user is driving the SOURCE (exact-in): reflect the quote's OUTPUT even
|
|
816
|
+
// when a fixed destinationTokenAmount pre-filled the receive. That lock only
|
|
817
|
+
// governs the INITIAL exact-out state (untouched source → compute the pay for
|
|
818
|
+
// the fixed receive). Once the user edits the pay, keeping the receive pinned
|
|
819
|
+
// shows a value that doesn't match the quote — the URL amount "overrides" the
|
|
820
|
+
// real output, an invalid mismatched swap.
|
|
821
|
+
const amount = anyspendQuote.data.currencyOut.amount;
|
|
822
|
+
const decimals = anyspendQuote.data.currencyOut.currency.decimals;
|
|
823
|
+
setDstAmount(formatTokenAmount(BigInt(amount), decimals, 6, false));
|
|
816
824
|
} else {
|
|
817
825
|
const amount = anyspendQuote.data.currencyIn.amount;
|
|
818
826
|
const decimals = anyspendQuote.data.currencyIn.currency.decimals;
|
|
@@ -820,15 +828,12 @@ function AnySpendInner({
|
|
|
820
828
|
}
|
|
821
829
|
} else {
|
|
822
830
|
if (isSrcInputDirty) {
|
|
823
|
-
|
|
824
|
-
if (!destinationTokenAmount) {
|
|
825
|
-
setDstAmount("");
|
|
826
|
-
}
|
|
831
|
+
setDstAmount("");
|
|
827
832
|
} else {
|
|
828
833
|
setSrcAmount("");
|
|
829
834
|
}
|
|
830
835
|
}
|
|
831
|
-
}, [anyspendQuote, isSrcInputDirty
|
|
836
|
+
}, [anyspendQuote, isSrcInputDirty]);
|
|
832
837
|
|
|
833
838
|
// Call onSuccess when order is executed
|
|
834
839
|
useOnOrderSuccess({ orderData: oat, orderId, onSuccess });
|
|
@@ -906,15 +911,36 @@ function AnySpendInner({
|
|
|
906
911
|
const isDirectTransfer = isSameChainSameToken && allowDirectTransfer;
|
|
907
912
|
|
|
908
913
|
// Determine button state and text
|
|
909
|
-
const btnInfo: {
|
|
914
|
+
const btnInfo: {
|
|
915
|
+
text: string;
|
|
916
|
+
disable: boolean;
|
|
917
|
+
error: boolean;
|
|
918
|
+
loading: boolean;
|
|
919
|
+
} = useMemo(() => {
|
|
910
920
|
// For fiat tab, check srcAmountOnRamp; for crypto tab, check activeInputAmountInWei
|
|
911
921
|
const hasAmount =
|
|
912
922
|
activeTab === "fiat" ? srcAmountOnRamp && parseFloat(srcAmountOnRamp) > 0 : activeInputAmountInWei !== "0";
|
|
913
|
-
if (!hasAmount)
|
|
923
|
+
if (!hasAmount)
|
|
924
|
+
return {
|
|
925
|
+
text: "Enter an amount",
|
|
926
|
+
disable: true,
|
|
927
|
+
error: false,
|
|
928
|
+
loading: false,
|
|
929
|
+
};
|
|
914
930
|
if (isSameChainSameToken && !allowDirectTransfer)
|
|
915
|
-
return {
|
|
931
|
+
return {
|
|
932
|
+
text: "Select a different token or chain",
|
|
933
|
+
disable: true,
|
|
934
|
+
error: false,
|
|
935
|
+
loading: false,
|
|
936
|
+
};
|
|
916
937
|
if (isLoadingAnyspendQuote && !isSameChainSameToken)
|
|
917
|
-
return {
|
|
938
|
+
return {
|
|
939
|
+
text: "Loading quote...",
|
|
940
|
+
disable: true,
|
|
941
|
+
error: false,
|
|
942
|
+
loading: true,
|
|
943
|
+
};
|
|
918
944
|
if (isCreatingOrder || isCreatingOnrampOrder || isSwitchingOrExecuting)
|
|
919
945
|
return {
|
|
920
946
|
text: isSwitchingOrExecuting ? "Transferring..." : "Creating order...",
|
|
@@ -923,15 +949,31 @@ function AnySpendInner({
|
|
|
923
949
|
loading: true,
|
|
924
950
|
};
|
|
925
951
|
if ((!anyspendQuote || !anyspendQuote.success) && !(isSameChainSameToken && allowDirectTransfer))
|
|
926
|
-
return {
|
|
952
|
+
return {
|
|
953
|
+
text: "No quote found",
|
|
954
|
+
disable: true,
|
|
955
|
+
error: false,
|
|
956
|
+
loading: false,
|
|
957
|
+
};
|
|
927
958
|
|
|
928
959
|
if (activeTab === "fiat") {
|
|
929
960
|
// For fiat: check recipient first, then payment method
|
|
930
|
-
if (!effectiveRecipientAddress)
|
|
961
|
+
if (!effectiveRecipientAddress)
|
|
962
|
+
return {
|
|
963
|
+
text: "Select recipient",
|
|
964
|
+
disable: false,
|
|
965
|
+
error: false,
|
|
966
|
+
loading: false,
|
|
967
|
+
};
|
|
931
968
|
|
|
932
969
|
// If no fiat payment method selected, show "Select payment method"
|
|
933
970
|
if (selectedFiatPaymentMethod === FiatPaymentMethod.NONE) {
|
|
934
|
-
return {
|
|
971
|
+
return {
|
|
972
|
+
text: "Select payment method",
|
|
973
|
+
disable: false,
|
|
974
|
+
error: false,
|
|
975
|
+
loading: false,
|
|
976
|
+
};
|
|
935
977
|
}
|
|
936
978
|
// If payment method is selected, show "Continue"
|
|
937
979
|
return { text: "Continue", disable: false, error: false, loading: false };
|
|
@@ -942,11 +984,22 @@ function AnySpendInner({
|
|
|
942
984
|
|
|
943
985
|
// If no payment method selected, show "Choose payment method"
|
|
944
986
|
if (effectiveCryptoPaymentMethod === CryptoPaymentMethodType.NONE) {
|
|
945
|
-
return {
|
|
987
|
+
return {
|
|
988
|
+
text: "Choose payment method",
|
|
989
|
+
disable: false,
|
|
990
|
+
error: false,
|
|
991
|
+
loading: false,
|
|
992
|
+
};
|
|
946
993
|
}
|
|
947
994
|
|
|
948
995
|
// Check recipient after payment method
|
|
949
|
-
if (!effectiveRecipientAddress)
|
|
996
|
+
if (!effectiveRecipientAddress)
|
|
997
|
+
return {
|
|
998
|
+
text: "Select recipient",
|
|
999
|
+
disable: false,
|
|
1000
|
+
error: false,
|
|
1001
|
+
loading: false,
|
|
1002
|
+
};
|
|
950
1003
|
|
|
951
1004
|
// If payment method selected, show appropriate action
|
|
952
1005
|
if (
|
|
@@ -954,10 +1007,22 @@ function AnySpendInner({
|
|
|
954
1007
|
effectiveCryptoPaymentMethod === CryptoPaymentMethodType.GLOBAL_WALLET
|
|
955
1008
|
) {
|
|
956
1009
|
const buttonText = isSameChainSameToken && allowDirectTransfer ? "Transfer" : "Swap";
|
|
957
|
-
return {
|
|
1010
|
+
return {
|
|
1011
|
+
text: buttonText,
|
|
1012
|
+
disable: false,
|
|
1013
|
+
error: false,
|
|
1014
|
+
loading: false,
|
|
1015
|
+
};
|
|
958
1016
|
}
|
|
959
1017
|
if (effectiveCryptoPaymentMethod === CryptoPaymentMethodType.TRANSFER_CRYPTO) {
|
|
960
|
-
|
|
1018
|
+
const transferText =
|
|
1019
|
+
isSameChainSameToken && allowDirectTransfer ? "Show deposit address" : "Continue to payment";
|
|
1020
|
+
return {
|
|
1021
|
+
text: transferText,
|
|
1022
|
+
disable: false,
|
|
1023
|
+
error: false,
|
|
1024
|
+
loading: false,
|
|
1025
|
+
};
|
|
961
1026
|
}
|
|
962
1027
|
}
|
|
963
1028
|
|
|
@@ -1063,10 +1128,18 @@ function AnySpendInner({
|
|
|
1063
1128
|
// Handle crypto swap creation
|
|
1064
1129
|
const handleCryptoSwap = async (method: CryptoPaymentMethodType) => {
|
|
1065
1130
|
try {
|
|
1066
|
-
|
|
1131
|
+
// TRANSFER_CRYPTO is the manual deposit-address flow — no connected wallet to send from.
|
|
1132
|
+
const isDirectTransfer =
|
|
1133
|
+
isSameChainSameToken && allowDirectTransfer && method !== CryptoPaymentMethodType.TRANSFER_CRYPTO;
|
|
1067
1134
|
|
|
1068
1135
|
invariant(effectiveRecipientAddress, "Recipient address is not found");
|
|
1069
1136
|
|
|
1137
|
+
// Same-token manual deposit → show recipient address + watch, no relayer order, no fee
|
|
1138
|
+
if (isSameChainSameToken && allowDirectTransfer && method === CryptoPaymentMethodType.TRANSFER_CRYPTO) {
|
|
1139
|
+
navigateToPanel(PanelView.PURE_TRANSFER_ADDRESS, "forward");
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1070
1143
|
const srcAmountBigInt = parseUnits(srcAmount.replace(/,/g, ""), selectedSrcToken.decimals);
|
|
1071
1144
|
|
|
1072
1145
|
// Handle direct transfer (same chain/token) - bypass backend, transfer directly
|
|
@@ -1089,10 +1162,6 @@ function AnySpendInner({
|
|
|
1089
1162
|
// Regular swap flow - use backend
|
|
1090
1163
|
invariant(anyspendQuote, "Relay price is not found");
|
|
1091
1164
|
|
|
1092
|
-
// Debug: Check payment method values
|
|
1093
|
-
console.log("handleCryptoSwap - method parameter:", method);
|
|
1094
|
-
console.log("handleCryptoSwap - selectedCryptoPaymentMethod state:", selectedCryptoPaymentMethod);
|
|
1095
|
-
|
|
1096
1165
|
createOrder({
|
|
1097
1166
|
recipientAddress: effectiveRecipientAddress,
|
|
1098
1167
|
orderType: "swap",
|
|
@@ -1100,7 +1169,11 @@ function AnySpendInner({
|
|
|
1100
1169
|
dstChain: isBuyMode ? destinationTokenChainId : selectedDstChainId,
|
|
1101
1170
|
srcToken: selectedSrcToken,
|
|
1102
1171
|
dstToken: isBuyMode
|
|
1103
|
-
? {
|
|
1172
|
+
? {
|
|
1173
|
+
...selectedDstToken,
|
|
1174
|
+
chainId: destinationTokenChainId,
|
|
1175
|
+
address: destinationTokenAddress,
|
|
1176
|
+
}
|
|
1104
1177
|
: selectedDstToken,
|
|
1105
1178
|
srcAmount: srcAmountBigInt.toString(),
|
|
1106
1179
|
expectedDstAmount: anyspendQuote?.data?.currencyOut?.amount || "0",
|
|
@@ -1759,6 +1832,20 @@ function AnySpendInner({
|
|
|
1759
1832
|
</div>
|
|
1760
1833
|
);
|
|
1761
1834
|
|
|
1835
|
+
const pureTransferAddressView = (
|
|
1836
|
+
<PureTransferView
|
|
1837
|
+
mode={mode}
|
|
1838
|
+
recipientAddress={effectiveRecipientAddress ?? ""}
|
|
1839
|
+
chainId={selectedSrcChainId}
|
|
1840
|
+
token={selectedSrcToken}
|
|
1841
|
+
displayAmount={srcAmount && srcAmount !== "0" ? `${srcAmount} ${selectedSrcToken.symbol}` : undefined}
|
|
1842
|
+
isActive={activePanel === PanelView.PURE_TRANSFER_ADDRESS}
|
|
1843
|
+
onBack={navigateBack}
|
|
1844
|
+
onClose={() => setB3ModalOpen(false)}
|
|
1845
|
+
onTransferDetected={() => onSuccess?.()}
|
|
1846
|
+
/>
|
|
1847
|
+
);
|
|
1848
|
+
|
|
1762
1849
|
// Add tabs to the main component when no order is loaded
|
|
1763
1850
|
return (
|
|
1764
1851
|
<StyleRoot>
|
|
@@ -1839,6 +1926,9 @@ function AnySpendInner({
|
|
|
1839
1926
|
<div key="fiat-auth-view" className={cn(mode === "page" && "p-6")}>
|
|
1840
1927
|
{authView}
|
|
1841
1928
|
</div>,
|
|
1929
|
+
<div key="pure-transfer-address-view" className={cn(mode === "page" && "p-6")}>
|
|
1930
|
+
{pureTransferAddressView}
|
|
1931
|
+
</div>,
|
|
1842
1932
|
]}
|
|
1843
1933
|
</TransitionPanel>
|
|
1844
1934
|
</div>
|
|
@@ -574,7 +574,11 @@ function AnySpendCustomExactInInner({
|
|
|
574
574
|
|
|
575
575
|
const handleCryptoOrder = async () => {
|
|
576
576
|
try {
|
|
577
|
-
|
|
577
|
+
// TRANSFER_CRYPTO is the manual deposit-address flow — no connected wallet to send from.
|
|
578
|
+
const isDirectTransfer =
|
|
579
|
+
isSameChainSameToken &&
|
|
580
|
+
allowDirectTransfer &&
|
|
581
|
+
effectiveCryptoPaymentMethod !== CryptoPaymentMethodType.TRANSFER_CRYPTO;
|
|
578
582
|
|
|
579
583
|
if (!isDirectTransfer) {
|
|
580
584
|
invariant(anyspendQuote, "Relay price is not found");
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getChainName, getPaymentUrl, ZERO_ADDRESS } from "@b3dotfun/sdk/anyspend";
|
|
4
|
+
import type { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
5
|
+
import { CopyToClipboard } from "@b3dotfun/sdk/global-account/react";
|
|
6
|
+
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
7
|
+
import { ChevronLeft, Loader2, X } from "lucide-react";
|
|
8
|
+
import { QRCodeSVG } from "qrcode.react";
|
|
9
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
10
|
+
import type { TransferResult } from "../../hooks/useWatchTransfer";
|
|
11
|
+
import { useWatchTransfer } from "../../hooks/useWatchTransfer";
|
|
12
|
+
import { ChainWarningText, WarningText } from "./WarningText";
|
|
13
|
+
import { TransferResultScreen } from "./TransferResultScreen";
|
|
14
|
+
|
|
15
|
+
export interface PureTransferViewProps {
|
|
16
|
+
mode?: "modal" | "page";
|
|
17
|
+
/** The recipient address where funds will land (the user's own wallet) */
|
|
18
|
+
recipientAddress: string;
|
|
19
|
+
/** Chain ID to watch on */
|
|
20
|
+
chainId: number;
|
|
21
|
+
/** Token the user sends — drives the QR code and balance watcher */
|
|
22
|
+
token: components["schemas"]["Token"];
|
|
23
|
+
/** Human-readable amount to display (display-only; skip row if empty or "0") */
|
|
24
|
+
displayAmount?: string;
|
|
25
|
+
/** Whether this panel is the currently active panel; gates the balance watcher */
|
|
26
|
+
isActive?: boolean;
|
|
27
|
+
/** Callback when back chevron is clicked */
|
|
28
|
+
onBack?: () => void;
|
|
29
|
+
/** Callback when X close button is clicked */
|
|
30
|
+
onClose?: () => void;
|
|
31
|
+
/** Callback when an incoming transfer is detected */
|
|
32
|
+
onTransferDetected?: (result: TransferResult) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A self-contained view that shows the recipient address as a QR code + copy button and
|
|
37
|
+
* watches for an incoming same-chain/same-token transfer via `useWatchTransfer`. When a
|
|
38
|
+
* transfer is detected it renders `TransferResultScreen`.
|
|
39
|
+
*
|
|
40
|
+
* This is the "TRANSFER_CRYPTO" path for same-token AnySpend deposits — no relayer order,
|
|
41
|
+
* no fee; the user simply sends the token directly to their own address.
|
|
42
|
+
*/
|
|
43
|
+
export function PureTransferView({
|
|
44
|
+
mode = "modal",
|
|
45
|
+
recipientAddress,
|
|
46
|
+
chainId,
|
|
47
|
+
token,
|
|
48
|
+
displayAmount,
|
|
49
|
+
isActive,
|
|
50
|
+
onBack,
|
|
51
|
+
onClose,
|
|
52
|
+
onTransferDetected,
|
|
53
|
+
}: PureTransferViewProps) {
|
|
54
|
+
const [transferResult, setTransferResult] = useState<TransferResult | null>(null);
|
|
55
|
+
|
|
56
|
+
// Fix 4: reset transferResult when the panel becomes active again
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (isActive) setTransferResult(null);
|
|
59
|
+
}, [isActive]);
|
|
60
|
+
|
|
61
|
+
// Fix 3: stabilize the callback so it doesn't re-run the watcher effect every render
|
|
62
|
+
const handleTransferDetected = useCallback(
|
|
63
|
+
(result: TransferResult) => {
|
|
64
|
+
setTransferResult(result);
|
|
65
|
+
onTransferDetected?.(result);
|
|
66
|
+
},
|
|
67
|
+
[onTransferDetected],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const { isWatching } = useWatchTransfer({
|
|
71
|
+
address: recipientAddress,
|
|
72
|
+
chainId,
|
|
73
|
+
tokenAddress: token.address,
|
|
74
|
+
tokenDecimals: token.decimals,
|
|
75
|
+
// Fix 1: also require a non-empty recipient before enabling the watcher
|
|
76
|
+
enabled: !!recipientAddress && !transferResult && isActive,
|
|
77
|
+
onTransferDetected: handleTransferDetected,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Fix 2: guard qrValue when recipientAddress is empty
|
|
81
|
+
const qrValue = useMemo(
|
|
82
|
+
() =>
|
|
83
|
+
recipientAddress
|
|
84
|
+
? getPaymentUrl(
|
|
85
|
+
recipientAddress,
|
|
86
|
+
undefined,
|
|
87
|
+
token.address === ZERO_ADDRESS ? "ETH" : token.address,
|
|
88
|
+
chainId,
|
|
89
|
+
token.decimals,
|
|
90
|
+
)
|
|
91
|
+
: "",
|
|
92
|
+
[recipientAddress, token.address, token.decimals, chainId],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const chainName = useMemo(() => {
|
|
96
|
+
try {
|
|
97
|
+
return getChainName(chainId);
|
|
98
|
+
} catch {
|
|
99
|
+
return "the specified chain";
|
|
100
|
+
}
|
|
101
|
+
}, [chainId]);
|
|
102
|
+
|
|
103
|
+
// Guard: if no address yet, render nothing
|
|
104
|
+
if (!recipientAddress) return null;
|
|
105
|
+
|
|
106
|
+
// Once a transfer is detected, hand off to the shared success screen
|
|
107
|
+
if (transferResult) {
|
|
108
|
+
return (
|
|
109
|
+
<TransferResultScreen
|
|
110
|
+
mode={mode}
|
|
111
|
+
transferResult={transferResult}
|
|
112
|
+
token={token}
|
|
113
|
+
chainId={chainId}
|
|
114
|
+
recipientAddress={recipientAddress}
|
|
115
|
+
onBack={onBack}
|
|
116
|
+
onClose={onClose}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
className={cn(
|
|
124
|
+
"anyspend-container anyspend-pure-transfer font-inter bg-as-surface-primary mx-auto w-full max-w-[460px] p-6",
|
|
125
|
+
)}
|
|
126
|
+
>
|
|
127
|
+
<div className="anyspend-pure-transfer-content flex flex-col gap-4">
|
|
128
|
+
{/* Header */}
|
|
129
|
+
<div className="anyspend-pure-transfer-header flex items-center justify-between">
|
|
130
|
+
{onBack ? (
|
|
131
|
+
<button
|
|
132
|
+
onClick={onBack}
|
|
133
|
+
className="anyspend-pure-transfer-back text-as-secondary hover:text-as-primary"
|
|
134
|
+
aria-label="Go back"
|
|
135
|
+
>
|
|
136
|
+
<ChevronLeft className="h-5 w-5" />
|
|
137
|
+
</button>
|
|
138
|
+
) : (
|
|
139
|
+
<div className="w-5" />
|
|
140
|
+
)}
|
|
141
|
+
<h2 className="anyspend-pure-transfer-title text-as-primary text-base font-semibold">Send {token.symbol}</h2>
|
|
142
|
+
{onClose ? (
|
|
143
|
+
<button
|
|
144
|
+
onClick={onClose}
|
|
145
|
+
className="anyspend-pure-transfer-close text-as-secondary hover:text-as-primary"
|
|
146
|
+
aria-label="Close"
|
|
147
|
+
>
|
|
148
|
+
<X className="h-5 w-5" />
|
|
149
|
+
</button>
|
|
150
|
+
) : (
|
|
151
|
+
<div className="w-5" />
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Optional amount badge */}
|
|
156
|
+
{displayAmount && (
|
|
157
|
+
<div className="anyspend-pure-transfer-amount-badge border-as-border-secondary bg-as-surface-secondary flex items-center justify-center rounded-xl border px-4 py-2">
|
|
158
|
+
<span className="text-as-primary text-sm font-medium">{displayAmount}</span>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* QR Code and address block — mirrors QRDeposit ~lines 413-453 */}
|
|
163
|
+
<div className="anyspend-pure-transfer-qr-content border-as-stroke flex items-start gap-4 rounded-xl border p-4">
|
|
164
|
+
{/* QR Code */}
|
|
165
|
+
<div className="anyspend-pure-transfer-qr-code-container flex flex-col items-center gap-2">
|
|
166
|
+
<div className="anyspend-pure-transfer-qr-code rounded-lg bg-white p-2">
|
|
167
|
+
<QRCodeSVG value={qrValue} size={120} level="M" marginSize={0} />
|
|
168
|
+
</div>
|
|
169
|
+
<span className="anyspend-pure-transfer-qr-scan-hint text-as-secondary text-xs">
|
|
170
|
+
SCAN WITH <span className="inline-block">🦊</span>
|
|
171
|
+
</span>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Address info */}
|
|
175
|
+
<div className="anyspend-pure-transfer-address-container flex flex-1 flex-col gap-1">
|
|
176
|
+
<span className="anyspend-pure-transfer-address-label text-as-secondary text-sm">Deposit address:</span>
|
|
177
|
+
<div className="anyspend-pure-transfer-address-row flex items-start gap-1">
|
|
178
|
+
<span className="anyspend-pure-transfer-address text-as-primary break-all font-mono text-sm leading-relaxed">
|
|
179
|
+
{recipientAddress}
|
|
180
|
+
</span>
|
|
181
|
+
<CopyToClipboard
|
|
182
|
+
text={recipientAddress}
|
|
183
|
+
className="anyspend-pure-transfer-copy-icon text-as-secondary hover:text-as-primary mt-0.5 shrink-0"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Warnings — mirrors QRDeposit ~lines 456-460 */}
|
|
190
|
+
<ChainWarningText chainId={chainId} />
|
|
191
|
+
<WarningText>
|
|
192
|
+
Only send {token.symbol} on {chainName}. Other tokens will not be converted.
|
|
193
|
+
</WarningText>
|
|
194
|
+
|
|
195
|
+
{/* Watching indicator — mirrors QRDeposit ~lines 462-473 */}
|
|
196
|
+
{isWatching && (
|
|
197
|
+
<div className="anyspend-pure-transfer-watching bg-as-brand/10 flex items-center justify-center gap-2 rounded-lg p-3">
|
|
198
|
+
<Loader2 className="text-as-brand h-4 w-4 animate-spin" />
|
|
199
|
+
<span className="text-as-brand text-sm">Watching for incoming transfer...</span>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
|
|
203
|
+
{/* Full-width copy button — mirrors QRDeposit ~lines 476-484 */}
|
|
204
|
+
<CopyToClipboard text={recipientAddress}>
|
|
205
|
+
<button className="anyspend-pure-transfer-copy-button bg-as-brand hover:bg-as-brand/90 flex w-full items-center justify-center gap-2 rounded-xl py-3.5 font-medium text-white transition-all">
|
|
206
|
+
Copy deposit address
|
|
207
|
+
</button>
|
|
208
|
+
</CopyToClipboard>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -19,6 +19,37 @@ let socketClientUrl: string | null = null;
|
|
|
19
19
|
let restClient: ClientApplication | null = null;
|
|
20
20
|
let restClientUrl: string | null = null;
|
|
21
21
|
|
|
22
|
+
// The pinned @b3dotfun/b3-api client's generated usersMethods predates `setBio`,
|
|
23
|
+
// so `users.setBio(...)` throws "setBio is not a function". Re-register the users
|
|
24
|
+
// service with the full list (mirrors users.shared.ts). Remove once the
|
|
25
|
+
// @b3dotfun/b3-api pin includes setBio.
|
|
26
|
+
const USERS_METHODS = [
|
|
27
|
+
"find",
|
|
28
|
+
"get",
|
|
29
|
+
"create",
|
|
30
|
+
"patch",
|
|
31
|
+
"remove",
|
|
32
|
+
"setReferralCode",
|
|
33
|
+
"syncTwProfiles",
|
|
34
|
+
"setAvatar",
|
|
35
|
+
"setBio",
|
|
36
|
+
"registerUsername",
|
|
37
|
+
"getMe",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Re-register on every client the manager builds — the active transport
|
|
41
|
+
// (REST/socket) switches at runtime.
|
|
42
|
+
function ensureUsersCustomMethods(client: ClientApplication): void {
|
|
43
|
+
try {
|
|
44
|
+
const connection = (client as any).get("connection");
|
|
45
|
+
if (connection?.service) {
|
|
46
|
+
(client as any).use("users", connection.service("users"), { methods: USERS_METHODS });
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Non-fatal: keep whatever methods the generated client already exposes.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
/**
|
|
23
54
|
* Creates a socket client
|
|
24
55
|
*/
|
|
@@ -27,6 +58,7 @@ function createSocketClient(): ClientApplication {
|
|
|
27
58
|
if (!socketClient || socketClientUrl !== url) {
|
|
28
59
|
socketInstance = io(url, { transports: ["websocket"] });
|
|
29
60
|
socketClient = createClient(socketio(socketInstance), clientOptions);
|
|
61
|
+
ensureUsersCustomMethods(socketClient);
|
|
30
62
|
socketClientUrl = url;
|
|
31
63
|
}
|
|
32
64
|
return socketClient;
|
|
@@ -48,6 +80,7 @@ function createRestClient(): ClientApplication {
|
|
|
48
80
|
if (!restClient || restClientUrl !== url) {
|
|
49
81
|
const connection = rest(url).fetch(resolveFetch());
|
|
50
82
|
restClient = createClient(connection, clientOptions);
|
|
83
|
+
ensureUsersCustomMethods(restClient);
|
|
51
84
|
restClientUrl = url;
|
|
52
85
|
}
|
|
53
86
|
return restClient;
|