@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.
Files changed (116) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -0
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +7 -16
  3. package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
  4. package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.js +151 -22
  5. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +4 -50
  6. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.d.ts +2 -0
  7. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -2
  8. package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +3 -1
  9. package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +2 -2
  10. package/dist/cjs/anyspend/react/components/AnySpendWorkflowTrigger.d.ts +31 -0
  11. package/dist/cjs/anyspend/react/components/AnySpendWorkflowTrigger.js +14 -0
  12. package/dist/cjs/anyspend/react/components/QRDeposit.js +5 -13
  13. package/dist/cjs/anyspend/react/components/ccShopAbi.d.ts +113 -0
  14. package/dist/cjs/anyspend/react/components/ccShopAbi.js +63 -0
  15. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +1 -3
  16. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +3 -3
  17. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +1 -4
  18. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +3 -57
  19. package/dist/cjs/anyspend/react/components/common/PaySection.js +1 -1
  20. package/dist/cjs/anyspend/react/components/index.d.ts +2 -0
  21. package/dist/cjs/anyspend/react/components/index.js +3 -1
  22. package/dist/cjs/anyspend/react/hooks/index.d.ts +1 -0
  23. package/dist/cjs/anyspend/react/hooks/index.js +1 -0
  24. package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +1 -0
  25. package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOrder.d.ts +1 -0
  26. package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOrder.js +1 -0
  27. package/dist/cjs/anyspend/react/hooks/useOnOrderSuccess.d.ts +10 -0
  28. package/dist/cjs/anyspend/react/hooks/useOnOrderSuccess.js +27 -0
  29. package/dist/cjs/anyspend/services/anyspend.d.ts +2 -1
  30. package/dist/cjs/anyspend/services/anyspend.js +2 -1
  31. package/dist/cjs/anyspend/utils/chain.d.ts +1 -1
  32. package/dist/cjs/anyspend/utils/chain.js +72 -62
  33. package/dist/cjs/global-account/react/components/B3DynamicModal.js +4 -0
  34. package/dist/cjs/global-account/react/hooks/useUserQuery.js +10 -0
  35. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +37 -1
  36. package/dist/cjs/global-account/react/stores/userStore.js +1 -0
  37. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -0
  38. package/dist/esm/anyspend/react/components/AnySpend.js +7 -16
  39. package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
  40. package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.js +152 -23
  41. package/dist/esm/anyspend/react/components/AnySpendCustom.js +4 -17
  42. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.d.ts +2 -0
  43. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +4 -2
  44. package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +3 -1
  45. package/dist/esm/anyspend/react/components/AnySpendDeposit.js +2 -2
  46. package/dist/esm/anyspend/react/components/AnySpendWorkflowTrigger.d.ts +31 -0
  47. package/dist/esm/anyspend/react/components/AnySpendWorkflowTrigger.js +11 -0
  48. package/dist/esm/anyspend/react/components/QRDeposit.js +6 -14
  49. package/dist/esm/anyspend/react/components/ccShopAbi.d.ts +113 -0
  50. package/dist/esm/anyspend/react/components/ccShopAbi.js +60 -0
  51. package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +1 -3
  52. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +3 -3
  53. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +1 -4
  54. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +2 -56
  55. package/dist/esm/anyspend/react/components/common/PaySection.js +1 -1
  56. package/dist/esm/anyspend/react/components/index.d.ts +2 -0
  57. package/dist/esm/anyspend/react/components/index.js +1 -0
  58. package/dist/esm/anyspend/react/hooks/index.d.ts +1 -0
  59. package/dist/esm/anyspend/react/hooks/index.js +1 -0
  60. package/dist/esm/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +1 -0
  61. package/dist/esm/anyspend/react/hooks/useAnyspendCreateOrder.d.ts +1 -0
  62. package/dist/esm/anyspend/react/hooks/useAnyspendCreateOrder.js +1 -0
  63. package/dist/esm/anyspend/react/hooks/useOnOrderSuccess.d.ts +10 -0
  64. package/dist/esm/anyspend/react/hooks/useOnOrderSuccess.js +24 -0
  65. package/dist/esm/anyspend/services/anyspend.d.ts +2 -1
  66. package/dist/esm/anyspend/services/anyspend.js +2 -1
  67. package/dist/esm/anyspend/utils/chain.d.ts +1 -1
  68. package/dist/esm/anyspend/utils/chain.js +72 -62
  69. package/dist/esm/global-account/react/components/B3DynamicModal.js +4 -0
  70. package/dist/esm/global-account/react/hooks/useUserQuery.js +11 -1
  71. package/dist/esm/global-account/react/stores/useModalStore.d.ts +37 -1
  72. package/dist/esm/global-account/react/stores/userStore.js +1 -0
  73. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -0
  74. package/dist/types/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
  75. package/dist/types/anyspend/react/components/AnySpendCustomExactIn.d.ts +2 -0
  76. package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +3 -1
  77. package/dist/types/anyspend/react/components/AnySpendWorkflowTrigger.d.ts +31 -0
  78. package/dist/types/anyspend/react/components/ccShopAbi.d.ts +113 -0
  79. package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +1 -3
  80. package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +1 -4
  81. package/dist/types/anyspend/react/components/index.d.ts +2 -0
  82. package/dist/types/anyspend/react/hooks/index.d.ts +1 -0
  83. package/dist/types/anyspend/react/hooks/useAnyspendCreateOrder.d.ts +1 -0
  84. package/dist/types/anyspend/react/hooks/useOnOrderSuccess.d.ts +10 -0
  85. package/dist/types/anyspend/services/anyspend.d.ts +2 -1
  86. package/dist/types/anyspend/utils/chain.d.ts +1 -1
  87. package/dist/types/global-account/react/stores/useModalStore.d.ts +37 -1
  88. package/package.json +1 -1
  89. package/src/anyspend/README.md +14 -0
  90. package/src/anyspend/docs/checkout-sessions.md +228 -0
  91. package/src/anyspend/docs/components.md +26 -0
  92. package/src/anyspend/docs/examples.md +58 -0
  93. package/src/anyspend/docs/hooks.md +32 -0
  94. package/src/anyspend/llms.txt +185 -0
  95. package/src/anyspend/react/components/AnySpend.tsx +9 -17
  96. package/src/anyspend/react/components/AnySpendCollectorClubPurchase.tsx +206 -22
  97. package/src/anyspend/react/components/AnySpendCustom.tsx +3 -18
  98. package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +5 -1
  99. package/src/anyspend/react/components/AnySpendDeposit.tsx +5 -0
  100. package/src/anyspend/react/components/AnySpendWorkflowTrigger.tsx +73 -0
  101. package/src/anyspend/react/components/QRDeposit.tsx +19 -15
  102. package/src/anyspend/react/components/ccShopAbi.ts +64 -0
  103. package/src/anyspend/react/components/common/CryptoPaySection.tsx +0 -5
  104. package/src/anyspend/react/components/common/OrderTokenAmount.tsx +1 -70
  105. package/src/anyspend/react/components/common/PaySection.tsx +0 -1
  106. package/src/anyspend/react/components/index.ts +2 -0
  107. package/src/anyspend/react/hooks/index.ts +1 -0
  108. package/src/anyspend/react/hooks/useAnyspendCreateOnrampOrder.ts +1 -0
  109. package/src/anyspend/react/hooks/useAnyspendCreateOrder.ts +2 -0
  110. package/src/anyspend/react/hooks/useOnOrderSuccess.ts +36 -0
  111. package/src/anyspend/services/anyspend.ts +3 -0
  112. package/src/anyspend/utils/chain.ts +81 -65
  113. package/src/global-account/react/components/B3DynamicModal.tsx +4 -0
  114. package/src/global-account/react/hooks/useUserQuery.ts +12 -1
  115. package/src/global-account/react/stores/useModalStore.ts +39 -2
  116. 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 { ALL_CHAINS, getAvailableChainIds, isSameChainAndToken } from "@b3dotfun/sdk/anyspend";
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
- useEffect(() => {
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={displayAddress} size={120} level="M" marginSize={0} />
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 { getNativeRequired } from "@b3dotfun/sdk/anyspend/utils/chain";
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
 
@@ -116,7 +116,6 @@ export function CryptoPaySection({
116
116
  </div>
117
117
  <OrderTokenAmount
118
118
  address={connectedAddress}
119
- walletAddress={connectedAddress}
120
119
  context="from"
121
120
  inputValue={srcAmount}
122
121
  onChangeInput={value => {
@@ -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(address: string, amount: bigint, currency: string, chainId: number, decimals?: number) {
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
- let displayAmount: string;
457
- if (decimals !== undefined && currency !== chain.nativeToken.symbol) {
458
- // Convert from smallest unit to display unit for ERC20 tokens
459
- const divisor = BigInt(10 ** decimals);
460
- const wholePart = amount / divisor;
461
- const fractionalPart = amount % divisor;
462
-
463
- if (fractionalPart === BigInt(0)) {
464
- displayAmount = wholePart.toString();
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
- // Format fractional part with leading zeros if needed
467
- const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
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
- tokenParams.append("amount", displayAmount);
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
- nativeParams.append("value", amount.toString());
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
- let displayAmount: string;
521
- if (decimals !== undefined) {
522
- const divisor = BigInt(10 ** decimals);
523
- const wholePart = amount / divisor;
524
- const fractionalPart = amount % divisor;
525
-
526
- if (fractionalPart === BigInt(0)) {
527
- displayAmount = wholePart.toString();
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
- const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
530
- const trimmedFractional = fractionalStr.replace(/0+$/, "");
531
- displayAmount = trimmedFractional ? `${wholePart}.${trimmedFractional}` : wholePart.toString();
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
- if (fractionalPart === BigInt(0)) {
540
- displayAmount = wholePart.toString();
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
- let displayAmount: string;
553
- if (decimals !== undefined) {
554
- const divisor = BigInt(10 ** decimals);
555
- const wholePart = amount / divisor;
556
- const fractionalPart = amount % divisor;
557
-
558
- if (fractionalPart === BigInt(0)) {
559
- displayAmount = wholePart.toString();
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
- const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
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
- params.append("amount", displayAmount);
583
+ params.append("amount", displayAmount);
584
+ }
570
585
  params.append("spl-token", currency); // token mint address
571
586
  }
572
587
 
573
- const url = `solana:${address}?${params.toString()}`;
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
  }