@b3dotfun/sdk 0.1.65 → 0.1.66-alpha.0
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 -0
- package/dist/cjs/anyspend/react/components/AnySpend.js +7 -16
- package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
- package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.js +151 -22
- package/dist/cjs/anyspend/react/components/AnySpendCustom.js +4 -50
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.d.ts +2 -0
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -2
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +2 -2
- package/dist/cjs/anyspend/react/components/AnySpendWorkflowTrigger.d.ts +31 -0
- package/dist/cjs/anyspend/react/components/AnySpendWorkflowTrigger.js +14 -0
- package/dist/cjs/anyspend/react/components/QRDeposit.js +5 -13
- package/dist/cjs/anyspend/react/components/ccShopAbi.d.ts +113 -0
- package/dist/cjs/anyspend/react/components/ccShopAbi.js +63 -0
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +1 -3
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +3 -3
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +1 -4
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +3 -57
- package/dist/cjs/anyspend/react/components/common/PaySection.js +1 -1
- package/dist/cjs/anyspend/react/components/index.d.ts +2 -0
- package/dist/cjs/anyspend/react/components/index.js +3 -1
- package/dist/cjs/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/cjs/anyspend/react/hooks/index.js +1 -0
- package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +1 -0
- package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOrder.d.ts +1 -0
- package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOrder.js +1 -0
- package/dist/cjs/anyspend/react/hooks/useOnOrderSuccess.d.ts +10 -0
- package/dist/cjs/anyspend/react/hooks/useOnOrderSuccess.js +27 -0
- package/dist/cjs/anyspend/services/anyspend.d.ts +2 -1
- package/dist/cjs/anyspend/services/anyspend.js +2 -1
- package/dist/cjs/anyspend/utils/chain.d.ts +1 -1
- package/dist/cjs/anyspend/utils/chain.js +72 -62
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +4 -0
- package/dist/cjs/global-account/react/hooks/useUserQuery.js +10 -0
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +37 -1
- package/dist/cjs/global-account/react/stores/userStore.js +1 -0
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +7 -16
- package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
- package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.js +152 -23
- package/dist/esm/anyspend/react/components/AnySpendCustom.js +4 -17
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.d.ts +2 -0
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +4 -2
- package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +3 -1
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +2 -2
- package/dist/esm/anyspend/react/components/AnySpendWorkflowTrigger.d.ts +31 -0
- package/dist/esm/anyspend/react/components/AnySpendWorkflowTrigger.js +11 -0
- package/dist/esm/anyspend/react/components/QRDeposit.js +6 -14
- package/dist/esm/anyspend/react/components/ccShopAbi.d.ts +113 -0
- package/dist/esm/anyspend/react/components/ccShopAbi.js +60 -0
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +1 -3
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +3 -3
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +1 -4
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +2 -56
- package/dist/esm/anyspend/react/components/common/PaySection.js +1 -1
- package/dist/esm/anyspend/react/components/index.d.ts +2 -0
- package/dist/esm/anyspend/react/components/index.js +1 -0
- package/dist/esm/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/esm/anyspend/react/hooks/index.js +1 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +1 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendCreateOrder.d.ts +1 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendCreateOrder.js +1 -0
- package/dist/esm/anyspend/react/hooks/useOnOrderSuccess.d.ts +10 -0
- package/dist/esm/anyspend/react/hooks/useOnOrderSuccess.js +24 -0
- package/dist/esm/anyspend/services/anyspend.d.ts +2 -1
- package/dist/esm/anyspend/services/anyspend.js +2 -1
- package/dist/esm/anyspend/utils/chain.d.ts +1 -1
- package/dist/esm/anyspend/utils/chain.js +72 -62
- package/dist/esm/global-account/react/components/B3DynamicModal.js +4 -0
- package/dist/esm/global-account/react/hooks/useUserQuery.js +11 -1
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +37 -1
- package/dist/esm/global-account/react/stores/userStore.js +1 -0
- package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/types/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
- package/dist/types/anyspend/react/components/AnySpendCustomExactIn.d.ts +2 -0
- package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +3 -1
- package/dist/types/anyspend/react/components/AnySpendWorkflowTrigger.d.ts +31 -0
- package/dist/types/anyspend/react/components/ccShopAbi.d.ts +113 -0
- package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +1 -3
- package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +1 -4
- package/dist/types/anyspend/react/components/index.d.ts +2 -0
- package/dist/types/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/types/anyspend/react/hooks/useAnyspendCreateOrder.d.ts +1 -0
- package/dist/types/anyspend/react/hooks/useOnOrderSuccess.d.ts +10 -0
- package/dist/types/anyspend/services/anyspend.d.ts +2 -1
- package/dist/types/anyspend/utils/chain.d.ts +1 -1
- package/dist/types/global-account/react/stores/useModalStore.d.ts +37 -1
- package/package.json +1 -1
- package/src/anyspend/README.md +14 -0
- package/src/anyspend/docs/checkout-sessions.md +228 -0
- package/src/anyspend/docs/components.md +26 -0
- package/src/anyspend/docs/examples.md +58 -0
- package/src/anyspend/docs/hooks.md +32 -0
- package/src/anyspend/llms.txt +185 -0
- package/src/anyspend/react/components/AnySpend.tsx +9 -17
- package/src/anyspend/react/components/AnySpendCollectorClubPurchase.tsx +206 -22
- package/src/anyspend/react/components/AnySpendCustom.tsx +3 -18
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +5 -1
- package/src/anyspend/react/components/AnySpendDeposit.tsx +5 -0
- package/src/anyspend/react/components/AnySpendWorkflowTrigger.tsx +73 -0
- package/src/anyspend/react/components/QRDeposit.tsx +19 -15
- package/src/anyspend/react/components/ccShopAbi.ts +64 -0
- package/src/anyspend/react/components/common/CryptoPaySection.tsx +0 -5
- package/src/anyspend/react/components/common/OrderTokenAmount.tsx +1 -70
- package/src/anyspend/react/components/common/PaySection.tsx +0 -1
- package/src/anyspend/react/components/index.ts +2 -0
- package/src/anyspend/react/hooks/index.ts +1 -0
- package/src/anyspend/react/hooks/useAnyspendCreateOnrampOrder.ts +1 -0
- package/src/anyspend/react/hooks/useAnyspendCreateOrder.ts +2 -0
- package/src/anyspend/react/hooks/useOnOrderSuccess.ts +36 -0
- package/src/anyspend/services/anyspend.ts +3 -0
- package/src/anyspend/utils/chain.ts +81 -65
- package/src/global-account/react/components/B3DynamicModal.tsx +4 -0
- package/src/global-account/react/hooks/useUserQuery.ts +12 -1
- package/src/global-account/react/stores/useModalStore.ts +39 -2
- package/src/global-account/react/stores/userStore.ts +1 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import type { AnySpendAllClasses } from "./types/classes";
|
|
3
|
+
import { AnySpendDeposit } from "./AnySpendDeposit";
|
|
4
|
+
|
|
5
|
+
export interface AnySpendWorkflowTriggerProps {
|
|
6
|
+
/** Payment recipient address (hex) */
|
|
7
|
+
recipientAddress: string;
|
|
8
|
+
/** Destination chain ID */
|
|
9
|
+
chainId: number;
|
|
10
|
+
/** Destination token address */
|
|
11
|
+
tokenAddress: string;
|
|
12
|
+
/** Required payment amount in token base units (wei) */
|
|
13
|
+
amount: string;
|
|
14
|
+
/** Workflow ID to trigger */
|
|
15
|
+
workflowId: string;
|
|
16
|
+
/** Organization ID that owns the workflow */
|
|
17
|
+
orgId: string;
|
|
18
|
+
/** Optional callback metadata merged into the order */
|
|
19
|
+
callbackMetadata?: {
|
|
20
|
+
/** Passed as trigger result inputs — accessible via {{root.result.inputs.*}} */
|
|
21
|
+
inputs?: Record<string, unknown>;
|
|
22
|
+
} & Record<string, unknown>;
|
|
23
|
+
/** Callback when payment succeeds */
|
|
24
|
+
onSuccess?: (amount: string) => void;
|
|
25
|
+
/** Callback when modal is closed */
|
|
26
|
+
onClose?: () => void;
|
|
27
|
+
/** Display mode */
|
|
28
|
+
mode?: "modal" | "page";
|
|
29
|
+
/** Custom action label */
|
|
30
|
+
actionLabel?: string;
|
|
31
|
+
/** Custom class names */
|
|
32
|
+
classes?: AnySpendAllClasses;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AnySpendWorkflowTrigger({
|
|
36
|
+
recipientAddress,
|
|
37
|
+
chainId,
|
|
38
|
+
tokenAddress,
|
|
39
|
+
amount,
|
|
40
|
+
workflowId,
|
|
41
|
+
orgId,
|
|
42
|
+
callbackMetadata,
|
|
43
|
+
onSuccess,
|
|
44
|
+
onClose,
|
|
45
|
+
mode,
|
|
46
|
+
actionLabel,
|
|
47
|
+
classes,
|
|
48
|
+
}: AnySpendWorkflowTriggerProps) {
|
|
49
|
+
const metadata = useMemo(
|
|
50
|
+
() => ({
|
|
51
|
+
workflowId,
|
|
52
|
+
orgId,
|
|
53
|
+
...callbackMetadata,
|
|
54
|
+
}),
|
|
55
|
+
[workflowId, orgId, callbackMetadata],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<AnySpendDeposit
|
|
60
|
+
recipientAddress={recipientAddress}
|
|
61
|
+
destinationTokenAddress={tokenAddress}
|
|
62
|
+
destinationTokenChainId={chainId}
|
|
63
|
+
destinationTokenAmount={amount}
|
|
64
|
+
callbackMetadata={metadata}
|
|
65
|
+
onSuccess={onSuccess}
|
|
66
|
+
onClose={onClose}
|
|
67
|
+
mode={mode}
|
|
68
|
+
actionLabel={actionLabel}
|
|
69
|
+
classes={classes}
|
|
70
|
+
allowDirectTransfer
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ALL_CHAINS,
|
|
3
|
+
getAvailableChainIds,
|
|
4
|
+
getPaymentUrl,
|
|
5
|
+
isSameChainAndToken,
|
|
6
|
+
ZERO_ADDRESS,
|
|
7
|
+
} from "@b3dotfun/sdk/anyspend";
|
|
2
8
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
3
9
|
import { Button, toast } from "@b3dotfun/sdk/global-account/react";
|
|
4
10
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
@@ -8,6 +14,7 @@ import { QRCodeSVG } from "qrcode.react";
|
|
|
8
14
|
import { useEffect, useRef, useState } from "react";
|
|
9
15
|
import { useAnyspendOrderAndTransactions } from "../hooks/useAnyspendOrderAndTransactions";
|
|
10
16
|
import { useCreateDepositFirstOrder } from "../hooks/useCreateDepositFirstOrder";
|
|
17
|
+
import { useOnOrderSuccess } from "../hooks/useOnOrderSuccess";
|
|
11
18
|
import { TransferResult, useWatchTransfer } from "../hooks/useWatchTransfer";
|
|
12
19
|
import { DepositContractConfig } from "./AnySpendDeposit";
|
|
13
20
|
import { ChainTokenIcon } from "./common/ChainTokenIcon";
|
|
@@ -90,7 +97,6 @@ export function QRDeposit({
|
|
|
90
97
|
const [orderId, setOrderId] = useState<string | undefined>();
|
|
91
98
|
const [globalAddress, setGlobalAddress] = useState<string | undefined>();
|
|
92
99
|
const orderCreatedRef = useRef(false);
|
|
93
|
-
const onSuccessCalled = useRef(false);
|
|
94
100
|
const [transferResult, setTransferResult] = useState<TransferResult | null>(null);
|
|
95
101
|
|
|
96
102
|
// Source token/chain as state (can be changed by user)
|
|
@@ -189,22 +195,20 @@ export function QRDeposit({
|
|
|
189
195
|
]);
|
|
190
196
|
|
|
191
197
|
// Call onSuccess when order is executed
|
|
192
|
-
|
|
193
|
-
if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
|
|
194
|
-
const txHash = oat?.data?.executeTx?.txHash;
|
|
195
|
-
onSuccess?.(txHash);
|
|
196
|
-
onSuccessCalled.current = true;
|
|
197
|
-
}
|
|
198
|
-
}, [oat?.data?.order.status, oat?.data?.executeTx?.txHash, onSuccess]);
|
|
199
|
-
|
|
200
|
-
// Reset onSuccess flag when orderId changes
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
onSuccessCalled.current = false;
|
|
203
|
-
}, [orderId]);
|
|
198
|
+
useOnOrderSuccess({ orderData: oat, orderId, onSuccess });
|
|
204
199
|
|
|
205
200
|
// For pure transfers, always use recipient address; for orders, use global address
|
|
206
201
|
const displayAddress = isPureTransfer ? recipientAddress : globalAddress || recipientAddress;
|
|
207
202
|
|
|
203
|
+
// Generate EIP-681 payment URI for the QR code so wallets know which chain/token to use
|
|
204
|
+
const qrValue = getPaymentUrl(
|
|
205
|
+
displayAddress,
|
|
206
|
+
undefined,
|
|
207
|
+
sourceToken.address === ZERO_ADDRESS ? "ETH" : sourceToken.address,
|
|
208
|
+
sourceChainId,
|
|
209
|
+
sourceToken.decimals,
|
|
210
|
+
);
|
|
211
|
+
|
|
208
212
|
const handleCopyAddress = async () => {
|
|
209
213
|
if (displayAddress) {
|
|
210
214
|
await navigator.clipboard.writeText(displayAddress);
|
|
@@ -387,7 +391,7 @@ export function QRDeposit({
|
|
|
387
391
|
{/* QR Code */}
|
|
388
392
|
<div className={classes?.qrCodeContainer || "anyspend-qr-code-container flex flex-col items-center gap-2"}>
|
|
389
393
|
<div className={classes?.qrCode || "anyspend-qr-code rounded-lg bg-white p-2"}>
|
|
390
|
-
<QRCodeSVG value={
|
|
394
|
+
<QRCodeSVG value={qrValue} size={120} level="M" marginSize={0} />
|
|
391
395
|
</div>
|
|
392
396
|
<span className={classes?.qrScanHint || "anyspend-qr-scan-hint text-as-secondary text-xs"}>
|
|
393
397
|
SCAN WITH <span className="inline-block">🦊</span>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// CCShop contract ABI fragments used by AnySpendCollectorClubPurchase
|
|
2
|
+
|
|
3
|
+
export const BUY_PACKS_FOR_ABI = {
|
|
4
|
+
inputs: [
|
|
5
|
+
{ internalType: "address", name: "user", type: "address" },
|
|
6
|
+
{ internalType: "uint256", name: "packId", type: "uint256" },
|
|
7
|
+
{ internalType: "uint256", name: "amount", type: "uint256" },
|
|
8
|
+
],
|
|
9
|
+
name: "buyPacksFor",
|
|
10
|
+
outputs: [],
|
|
11
|
+
stateMutability: "nonpayable",
|
|
12
|
+
type: "function",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export const BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
|
|
16
|
+
inputs: [
|
|
17
|
+
{ internalType: "address", name: "user", type: "address" },
|
|
18
|
+
{ internalType: "uint256", name: "packId", type: "uint256" },
|
|
19
|
+
{ internalType: "uint256", name: "amount", type: "uint256" },
|
|
20
|
+
{ internalType: "string", name: "discountCode", type: "string" },
|
|
21
|
+
],
|
|
22
|
+
name: "buyPacksForWithDiscount",
|
|
23
|
+
outputs: [],
|
|
24
|
+
stateMutability: "nonpayable",
|
|
25
|
+
type: "function",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export const IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI = {
|
|
29
|
+
inputs: [
|
|
30
|
+
{ internalType: "string", name: "code", type: "string" },
|
|
31
|
+
{ internalType: "uint256", name: "packId", type: "uint256" },
|
|
32
|
+
],
|
|
33
|
+
name: "isDiscountCodeValidForPack",
|
|
34
|
+
outputs: [
|
|
35
|
+
{ internalType: "bool", name: "isValid", type: "bool" },
|
|
36
|
+
{ internalType: "uint256", name: "discountAmount", type: "uint256" },
|
|
37
|
+
],
|
|
38
|
+
stateMutability: "view",
|
|
39
|
+
type: "function",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export const GET_DISCOUNT_CODE_ABI = {
|
|
43
|
+
inputs: [{ internalType: "string", name: "code", type: "string" }],
|
|
44
|
+
name: "getDiscountCode",
|
|
45
|
+
outputs: [
|
|
46
|
+
{
|
|
47
|
+
components: [
|
|
48
|
+
{ internalType: "uint256", name: "discountAmount", type: "uint256" },
|
|
49
|
+
{ internalType: "uint256", name: "expiresAt", type: "uint256" },
|
|
50
|
+
{ internalType: "bool", name: "used", type: "bool" },
|
|
51
|
+
{ internalType: "bool", name: "exists", type: "bool" },
|
|
52
|
+
{ internalType: "uint256", name: "maxUses", type: "uint256" },
|
|
53
|
+
{ internalType: "uint256", name: "usedCount", type: "uint256" },
|
|
54
|
+
{ internalType: "uint256", name: "packId", type: "uint256" },
|
|
55
|
+
{ internalType: "uint256", name: "minPurchaseAmount", type: "uint256" },
|
|
56
|
+
],
|
|
57
|
+
internalType: "struct CCShop.DiscountCode",
|
|
58
|
+
name: "",
|
|
59
|
+
type: "tuple",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
stateMutability: "view",
|
|
63
|
+
type: "function",
|
|
64
|
+
} as const;
|
|
@@ -32,8 +32,6 @@ interface CryptoPaySectionProps {
|
|
|
32
32
|
onShowFeeDetail?: () => void;
|
|
33
33
|
// Custom classes for styling
|
|
34
34
|
classes?: CryptoPaySectionClasses;
|
|
35
|
-
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
36
|
-
skipAutoMaxOnTokenChange?: boolean;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
export function CryptoPaySection({
|
|
@@ -51,7 +49,6 @@ export function CryptoPaySection({
|
|
|
51
49
|
onTokenSelect,
|
|
52
50
|
onShowFeeDetail,
|
|
53
51
|
classes,
|
|
54
|
-
skipAutoMaxOnTokenChange = false,
|
|
55
52
|
}: CryptoPaySectionProps) {
|
|
56
53
|
const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
|
|
57
54
|
|
|
@@ -125,7 +122,6 @@ export function CryptoPaySection({
|
|
|
125
122
|
<div className={classes?.inputContainer}>
|
|
126
123
|
<OrderTokenAmount
|
|
127
124
|
address={walletAddress}
|
|
128
|
-
walletAddress={walletAddress}
|
|
129
125
|
context="from"
|
|
130
126
|
inputValue={srcAmount}
|
|
131
127
|
onChangeInput={value => {
|
|
@@ -137,7 +133,6 @@ export function CryptoPaySection({
|
|
|
137
133
|
token={selectedSrcToken}
|
|
138
134
|
setToken={setSelectedSrcToken}
|
|
139
135
|
onTokenSelect={onTokenSelect}
|
|
140
|
-
skipAutoMaxOnTokenChange={skipAutoMaxOnTokenChange}
|
|
141
136
|
/>
|
|
142
137
|
</div>
|
|
143
138
|
<div className={classes?.balanceRow || "flex items-center justify-between"}>
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { ChevronsUpDown } from "lucide-react";
|
|
4
|
-
import { useEffect, useRef } from "react";
|
|
5
4
|
import { NumericFormat } from "react-number-format";
|
|
6
|
-
import { formatUnits } from "viem";
|
|
7
5
|
|
|
8
6
|
import { ALL_CHAINS, RELAY_SOLANA_MAINNET_CHAIN_ID, getAvailableChainIds } from "@b3dotfun/sdk/anyspend";
|
|
9
7
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
10
|
-
import {
|
|
11
|
-
import { isNativeToken } from "@b3dotfun/sdk/anyspend/utils/token";
|
|
12
|
-
import { Button, useTokenBalance } from "@b3dotfun/sdk/global-account/react";
|
|
8
|
+
import { Button } from "@b3dotfun/sdk/global-account/react";
|
|
13
9
|
import { cn } from "@b3dotfun/sdk/shared/utils";
|
|
14
10
|
import { TokenSelector } from "@relayprotocol/relay-kit-ui";
|
|
15
11
|
import { ChainTokenIcon } from "./ChainTokenIcon";
|
|
@@ -31,8 +27,6 @@ export function OrderTokenAmount({
|
|
|
31
27
|
amountClassName,
|
|
32
28
|
tokenSelectClassName,
|
|
33
29
|
onTokenSelect,
|
|
34
|
-
walletAddress,
|
|
35
|
-
skipAutoMaxOnTokenChange = false,
|
|
36
30
|
}: {
|
|
37
31
|
disabled?: boolean;
|
|
38
32
|
inputValue: string;
|
|
@@ -50,64 +44,7 @@ export function OrderTokenAmount({
|
|
|
50
44
|
amountClassName?: string;
|
|
51
45
|
tokenSelectClassName?: string;
|
|
52
46
|
onTokenSelect?: (token: components["schemas"]["Token"], event: { preventDefault: () => void }) => void;
|
|
53
|
-
walletAddress?: string | undefined;
|
|
54
|
-
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
55
|
-
skipAutoMaxOnTokenChange?: boolean;
|
|
56
47
|
}) {
|
|
57
|
-
// Track previous token to detect changes
|
|
58
|
-
const prevTokenRef = useRef<string>(token.address);
|
|
59
|
-
|
|
60
|
-
// Only get token balance when context is "from" (for setting max amount)
|
|
61
|
-
const { rawBalance } = useTokenBalance({
|
|
62
|
-
token,
|
|
63
|
-
address: context === "from" && walletAddress ? walletAddress : undefined,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
// Only handle "from" context
|
|
68
|
-
if (context !== "from") return;
|
|
69
|
-
|
|
70
|
-
// Skip auto-max when in fixed destination amount mode
|
|
71
|
-
if (skipAutoMaxOnTokenChange) {
|
|
72
|
-
prevTokenRef.current = token.address;
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Check if token changed or if this is the initial load with balance
|
|
77
|
-
const isTokenChanged = prevTokenRef.current !== token.address;
|
|
78
|
-
|
|
79
|
-
if (isTokenChanged && rawBalance) {
|
|
80
|
-
console.log(`Setting max balance - Token: ${token.address}, Changed: ${isTokenChanged}`);
|
|
81
|
-
|
|
82
|
-
// Calculate max amount with gas reserve for native tokens
|
|
83
|
-
let maxAmount: bigint;
|
|
84
|
-
|
|
85
|
-
if (isNativeToken(token.address)) {
|
|
86
|
-
const gasReserve = getNativeRequired(token.chainId);
|
|
87
|
-
// Ensure we don't go negative
|
|
88
|
-
maxAmount = rawBalance > gasReserve ? rawBalance - gasReserve : BigInt(0);
|
|
89
|
-
} else {
|
|
90
|
-
// For ERC20 tokens, use full balance
|
|
91
|
-
maxAmount = rawBalance;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Set the max amount as input value
|
|
95
|
-
onChangeInput(formatUnits(maxAmount, token.decimals));
|
|
96
|
-
|
|
97
|
-
// Update refs
|
|
98
|
-
prevTokenRef.current = token.address;
|
|
99
|
-
}
|
|
100
|
-
}, [
|
|
101
|
-
token.address,
|
|
102
|
-
token.chainId,
|
|
103
|
-
token.decimals,
|
|
104
|
-
chainId,
|
|
105
|
-
context,
|
|
106
|
-
onChangeInput,
|
|
107
|
-
rawBalance,
|
|
108
|
-
skipAutoMaxOnTokenChange,
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
48
|
const handleTokenSelect = (newToken: any) => {
|
|
112
49
|
const token: components["schemas"]["Token"] = {
|
|
113
50
|
address: newToken.address,
|
|
@@ -134,13 +71,7 @@ export function OrderTokenAmount({
|
|
|
134
71
|
}
|
|
135
72
|
}
|
|
136
73
|
|
|
137
|
-
// Mark that we're about to change tokens
|
|
138
|
-
prevTokenRef.current = "changing"; // Temporary value to force effect
|
|
139
|
-
|
|
140
|
-
// Set the chain ID first
|
|
141
74
|
setChainId(newToken.chainId);
|
|
142
|
-
|
|
143
|
-
// Then set the new token - the useEffect will handle setting the max balance
|
|
144
75
|
setToken(token);
|
|
145
76
|
};
|
|
146
77
|
|
|
@@ -24,6 +24,8 @@ export type {
|
|
|
24
24
|
RecipientSelectionClasses,
|
|
25
25
|
} from "./types/classes";
|
|
26
26
|
export { AnySpendDepositHype, HYPE_TOKEN_DETAILS } from "./AnyspendDepositHype";
|
|
27
|
+
export { AnySpendWorkflowTrigger } from "./AnySpendWorkflowTrigger";
|
|
28
|
+
export type { AnySpendWorkflowTriggerProps } from "./AnySpendWorkflowTrigger";
|
|
27
29
|
export * from "./AnySpendFingerprintWrapper";
|
|
28
30
|
export { AnySpendNFT } from "./AnySpendNFT";
|
|
29
31
|
export { AnyspendSignatureMint } from "./AnyspendSignatureMint";
|
|
@@ -12,6 +12,7 @@ export * from "./useGasPrice";
|
|
|
12
12
|
export * from "./useGeoOnrampOptions";
|
|
13
13
|
export * from "./useGetGeo";
|
|
14
14
|
export * from "./useHyperliquidTransfer";
|
|
15
|
+
export * from "./useOnOrderSuccess";
|
|
15
16
|
export * from "./useRecipientAddressState";
|
|
16
17
|
export * from "./useSigMint";
|
|
17
18
|
export * from "./useStripeClientSecret";
|
|
@@ -113,6 +113,7 @@ export function useAnyspendCreateOnrampOrder({ onSuccess, onError }: UseAnyspend
|
|
|
113
113
|
partnerId,
|
|
114
114
|
clientReferenceId,
|
|
115
115
|
visitorData,
|
|
116
|
+
callbackMetadata: params.callbackMetadata,
|
|
116
117
|
});
|
|
117
118
|
} catch (error: any) {
|
|
118
119
|
// If the error has a response with message and statusCode, throw that
|
|
@@ -22,6 +22,7 @@ export type CreateOrderParams = {
|
|
|
22
22
|
creatorAddress?: string;
|
|
23
23
|
payload?: any;
|
|
24
24
|
metadata?: Record<string, any>;
|
|
25
|
+
callbackMetadata?: Record<string, unknown>;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
export type UseAnyspendCreateOrderProps = {
|
|
@@ -87,6 +88,7 @@ export function useAnyspendCreateOrder({ onSuccess, onError }: UseAnyspendCreate
|
|
|
87
88
|
partnerId,
|
|
88
89
|
clientReferenceId,
|
|
89
90
|
visitorData,
|
|
91
|
+
callbackMetadata: params.callbackMetadata,
|
|
90
92
|
});
|
|
91
93
|
} catch (error: any) {
|
|
92
94
|
// If the error has a response with message and statusCode, throw that
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { GetOrderAndTxsResponse } from "../../types/api_req_res";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook to call onSuccess callback when an order is executed.
|
|
6
|
+
* Handles fallback to relayTxs when executeTx is null.
|
|
7
|
+
*/
|
|
8
|
+
export function useOnOrderSuccess({
|
|
9
|
+
orderData,
|
|
10
|
+
orderId,
|
|
11
|
+
onSuccess,
|
|
12
|
+
}: {
|
|
13
|
+
orderData: GetOrderAndTxsResponse | undefined;
|
|
14
|
+
orderId: string | undefined;
|
|
15
|
+
onSuccess?: (txHash?: string) => void;
|
|
16
|
+
}) {
|
|
17
|
+
const onSuccessCalled = useRef(false);
|
|
18
|
+
const prevOrderId = useRef(orderId);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
// Reset flag when orderId changes
|
|
22
|
+
if (prevOrderId.current !== orderId) {
|
|
23
|
+
onSuccessCalled.current = false;
|
|
24
|
+
prevOrderId.current = orderId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Call onSuccess when order is executed
|
|
28
|
+
if (orderData?.data?.order.status === "executed" && !onSuccessCalled.current) {
|
|
29
|
+
const relayTxs = orderData?.data?.relayTxs;
|
|
30
|
+
const lastSuccessfulRelayTx = relayTxs?.filter(tx => tx.status === "success").pop();
|
|
31
|
+
const txHash = orderData?.data?.executeTx?.txHash || lastSuccessfulRelayTx?.txHash;
|
|
32
|
+
onSuccess?.(txHash);
|
|
33
|
+
onSuccessCalled.current = true;
|
|
34
|
+
}
|
|
35
|
+
}, [orderData, orderId, onSuccess]);
|
|
36
|
+
}
|
|
@@ -70,6 +70,7 @@ export const anyspendService = {
|
|
|
70
70
|
partnerId,
|
|
71
71
|
clientReferenceId,
|
|
72
72
|
visitorData,
|
|
73
|
+
callbackMetadata,
|
|
73
74
|
}: {
|
|
74
75
|
recipientAddress: string;
|
|
75
76
|
type: string;
|
|
@@ -85,6 +86,7 @@ export const anyspendService = {
|
|
|
85
86
|
partnerId?: string;
|
|
86
87
|
clientReferenceId?: string;
|
|
87
88
|
visitorData?: VisitorData;
|
|
89
|
+
callbackMetadata?: Record<string, unknown>;
|
|
88
90
|
}) => {
|
|
89
91
|
const accessToken = await app.authentication.getAccessToken();
|
|
90
92
|
const response = await fetch(`${ANYSPEND_MAINNET_BASE_URL}/orders`, {
|
|
@@ -109,6 +111,7 @@ export const anyspendService = {
|
|
|
109
111
|
creatorAddress,
|
|
110
112
|
partnerId,
|
|
111
113
|
...(clientReferenceId && { clientReferenceId }),
|
|
114
|
+
...(callbackMetadata && { callbackMetadata }),
|
|
112
115
|
}),
|
|
113
116
|
});
|
|
114
117
|
const data: CreateOrderResponse = await response.json();
|
|
@@ -421,7 +421,13 @@ export function getCoingeckoName(chainId: number): string | null {
|
|
|
421
421
|
return ALL_CHAINS[chainId].coingeckoName;
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
export function getPaymentUrl(
|
|
424
|
+
export function getPaymentUrl(
|
|
425
|
+
address: string,
|
|
426
|
+
amount: bigint | undefined,
|
|
427
|
+
currency: string,
|
|
428
|
+
chainId: number,
|
|
429
|
+
decimals?: number,
|
|
430
|
+
) {
|
|
425
431
|
// Get chain type to determine URL format
|
|
426
432
|
const chainType = getChainType(chainId);
|
|
427
433
|
const chain = ALL_CHAINS[chainId];
|
|
@@ -433,8 +439,8 @@ export function getPaymentUrl(address: string, amount: bigint, currency: string,
|
|
|
433
439
|
// Format: ethereum:[address]@[chainId]?value=[amount]&symbol=[symbol]
|
|
434
440
|
const params = new URLSearchParams();
|
|
435
441
|
|
|
436
|
-
// Add value for native token transfers
|
|
437
|
-
if (currency === chain.nativeToken.symbol) {
|
|
442
|
+
// Add value for native token transfers (skip if amount not provided, e.g. deposit_first)
|
|
443
|
+
if (currency === chain.nativeToken.symbol && amount !== undefined) {
|
|
438
444
|
params.append("value", amount.toString());
|
|
439
445
|
}
|
|
440
446
|
|
|
@@ -453,28 +459,31 @@ export function getPaymentUrl(address: string, amount: bigint, currency: string,
|
|
|
453
459
|
|
|
454
460
|
// For ERC20 tokens, convert from smallest unit to display units using decimals
|
|
455
461
|
// For example: 2400623 (raw) with 6 decimals becomes "2.400623"
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
462
|
+
// Skip amount if not provided (e.g. deposit_first orders)
|
|
463
|
+
if (amount !== undefined) {
|
|
464
|
+
let displayAmount: string;
|
|
465
|
+
if (decimals !== undefined && currency !== chain.nativeToken.symbol) {
|
|
466
|
+
// Convert from smallest unit to display unit for ERC20 tokens
|
|
467
|
+
const divisor = BigInt(10 ** decimals);
|
|
468
|
+
const wholePart = amount / divisor;
|
|
469
|
+
const fractionalPart = amount % divisor;
|
|
470
|
+
|
|
471
|
+
if (fractionalPart === BigInt(0)) {
|
|
472
|
+
displayAmount = wholePart.toString();
|
|
473
|
+
} else {
|
|
474
|
+
// Format fractional part with leading zeros if needed
|
|
475
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
476
|
+
// Remove trailing zeros
|
|
477
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
478
|
+
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
479
|
+
}
|
|
465
480
|
} else {
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
// Remove trailing zeros
|
|
469
|
-
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
470
|
-
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
481
|
+
// For native tokens or when decimals not provided, use raw amount
|
|
482
|
+
displayAmount = amount.toString();
|
|
471
483
|
}
|
|
472
|
-
} else {
|
|
473
|
-
// For native tokens or when decimals not provided, use raw amount
|
|
474
|
-
displayAmount = amount.toString();
|
|
475
|
-
}
|
|
476
484
|
|
|
477
|
-
|
|
485
|
+
tokenParams.append("amount", displayAmount);
|
|
486
|
+
}
|
|
478
487
|
tokenParams.append("address", address); // recipient address
|
|
479
488
|
|
|
480
489
|
// For Arbitrum and other L2s, try a more explicit format
|
|
@@ -495,7 +504,9 @@ export function getPaymentUrl(address: string, amount: bigint, currency: string,
|
|
|
495
504
|
// to make sure wallets recognize the correct chain
|
|
496
505
|
const nativeParams = new URLSearchParams();
|
|
497
506
|
nativeParams.append("chainId", chainId.toString());
|
|
498
|
-
|
|
507
|
+
if (amount !== undefined) {
|
|
508
|
+
nativeParams.append("value", amount.toString());
|
|
509
|
+
}
|
|
499
510
|
const url = `ethereum:${address}@${chainId}?${nativeParams.toString()}`;
|
|
500
511
|
return url;
|
|
501
512
|
} else {
|
|
@@ -517,60 +528,65 @@ export function getPaymentUrl(address: string, amount: bigint, currency: string,
|
|
|
517
528
|
|
|
518
529
|
if (isNativeSOL) {
|
|
519
530
|
// Native SOL transfers - convert from lamports to SOL
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
531
|
+
if (amount !== undefined) {
|
|
532
|
+
let displayAmount: string;
|
|
533
|
+
if (decimals !== undefined) {
|
|
534
|
+
const divisor = BigInt(10 ** decimals);
|
|
535
|
+
const wholePart = amount / divisor;
|
|
536
|
+
const fractionalPart = amount % divisor;
|
|
537
|
+
|
|
538
|
+
if (fractionalPart === BigInt(0)) {
|
|
539
|
+
displayAmount = wholePart.toString();
|
|
540
|
+
} else {
|
|
541
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
542
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
543
|
+
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
544
|
+
}
|
|
528
545
|
} else {
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
|
|
546
|
+
// Fallback: assume SOL has 9 decimals
|
|
547
|
+
const divisor = BigInt(1000000000); // 1e9
|
|
548
|
+
const wholePart = amount / divisor;
|
|
549
|
+
const fractionalPart = amount % divisor;
|
|
550
|
+
|
|
551
|
+
if (fractionalPart === BigInt(0)) {
|
|
552
|
+
displayAmount = wholePart.toString();
|
|
553
|
+
} else {
|
|
554
|
+
const fractionalStr = fractionalPart.toString().padStart(9, "0");
|
|
555
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
556
|
+
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
557
|
+
}
|
|
532
558
|
}
|
|
533
|
-
} else {
|
|
534
|
-
// Fallback: assume SOL has 9 decimals
|
|
535
|
-
const divisor = BigInt(1000000000); // 1e9
|
|
536
|
-
const wholePart = amount / divisor;
|
|
537
|
-
const fractionalPart = amount % divisor;
|
|
538
559
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
} else {
|
|
542
|
-
const fractionalStr = fractionalPart.toString().padStart(9, "0");
|
|
543
|
-
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
544
|
-
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
545
|
-
}
|
|
560
|
+
// For native SOL, use simple format without spl-token parameter
|
|
561
|
+
params.append("amount", displayAmount);
|
|
546
562
|
}
|
|
547
|
-
|
|
548
|
-
// For native SOL, use simple format without spl-token parameter
|
|
549
|
-
params.append("amount", displayAmount);
|
|
550
563
|
} else {
|
|
551
564
|
// SPL token transfers
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
565
|
+
if (amount !== undefined) {
|
|
566
|
+
let displayAmount: string;
|
|
567
|
+
if (decimals !== undefined) {
|
|
568
|
+
const divisor = BigInt(10 ** decimals);
|
|
569
|
+
const wholePart = amount / divisor;
|
|
570
|
+
const fractionalPart = amount % divisor;
|
|
571
|
+
|
|
572
|
+
if (fractionalPart === BigInt(0)) {
|
|
573
|
+
displayAmount = wholePart.toString();
|
|
574
|
+
} else {
|
|
575
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
576
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
577
|
+
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
578
|
+
}
|
|
560
579
|
} else {
|
|
561
|
-
|
|
562
|
-
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
563
|
-
displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
|
|
580
|
+
displayAmount = amount.toString();
|
|
564
581
|
}
|
|
565
|
-
} else {
|
|
566
|
-
displayAmount = amount.toString();
|
|
567
|
-
}
|
|
568
582
|
|
|
569
|
-
|
|
583
|
+
params.append("amount", displayAmount);
|
|
584
|
+
}
|
|
570
585
|
params.append("spl-token", currency); // token mint address
|
|
571
586
|
}
|
|
572
587
|
|
|
573
|
-
const
|
|
588
|
+
const queryString = params.toString();
|
|
589
|
+
const url = queryString ? `solana:${address}?${queryString}` : `solana:${address}`;
|
|
574
590
|
console.log("Solana URL (isNativeSOL:", isNativeSOL, "):", url);
|
|
575
591
|
return url;
|
|
576
592
|
}
|