@rozoai/intent-pay 0.1.37-beta.1 → 0.1.37-beta.2
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/build/components/Common/TokenChainLogo/index.d.ts +1 -7
- package/build/components/Spinners/TokenLogoSpinner/index.d.ts +1 -6
- package/build/hooks/useDepositAddressOptions.d.ts +2 -3
- package/build/hooks/usePaymentState.d.ts +5 -11
- package/build/hooks/useRozoPay.d.ts +4 -4
- package/build/hooks/useSolanaPaymentOptions.d.ts +1 -1
- package/build/hooks/useStellarPaymentOptions.d.ts +1 -1
- package/build/hooks/useWalletPaymentOptions.d.ts +7 -12
- package/build/package.json.js +2 -2
- package/build/payment/createPaymentPayload.d.ts +3 -3
- package/build/payment/paymentFsm.d.ts +2 -25
- package/build/provider/PayContext.d.ts +3 -3
- package/build/src/components/Common/AmountInput/index.js +1 -1
- package/build/src/components/Common/AmountInput/index.js.map +1 -1
- package/build/src/components/Common/ConnectorList/index.js +3 -2
- package/build/src/components/Common/ConnectorList/index.js.map +1 -1
- package/build/src/components/Common/ScrollArea/index.js +3 -1
- package/build/src/components/Common/ScrollArea/index.js.map +1 -1
- package/build/src/components/Common/TokenChainLogo/index.js +3 -26
- package/build/src/components/Common/TokenChainLogo/index.js.map +1 -1
- package/build/src/components/Pages/PayWithToken/index.js +4 -15
- package/build/src/components/Pages/PayWithToken/index.js.map +1 -1
- package/build/src/components/Pages/SelectDepositAddressChain/index.js +17 -6
- package/build/src/components/Pages/SelectDepositAddressChain/index.js.map +1 -1
- package/build/src/components/Pages/SelectMethod/index.js +10 -9
- package/build/src/components/Pages/SelectMethod/index.js.map +1 -1
- package/build/src/components/Pages/SelectToken/index.js +3 -1
- package/build/src/components/Pages/SelectToken/index.js.map +1 -1
- package/build/src/components/Pages/Solana/PayWithSolanaToken/index.js +24 -57
- package/build/src/components/Pages/Solana/PayWithSolanaToken/index.js.map +1 -1
- package/build/src/components/Pages/Stellar/PayWithStellarToken/index.js +36 -33
- package/build/src/components/Pages/Stellar/PayWithStellarToken/index.js.map +1 -1
- package/build/src/components/Pages/WaitingDepositAddress/index.js +33 -110
- package/build/src/components/Pages/WaitingDepositAddress/index.js.map +1 -1
- package/build/src/components/RozoPayModal/index.js +26 -2
- package/build/src/components/RozoPayModal/index.js.map +1 -1
- package/build/src/components/Spinners/TokenLogoSpinner/index.js +2 -5
- package/build/src/components/Spinners/TokenLogoSpinner/index.js.map +1 -1
- package/build/src/hooks/useDepositAddressOptions.js +9 -19
- package/build/src/hooks/useDepositAddressOptions.js.map +1 -1
- package/build/src/hooks/usePaymentState.js +71 -185
- package/build/src/hooks/usePaymentState.js.map +1 -1
- package/build/src/hooks/useRozoPay.js +2 -4
- package/build/src/hooks/useRozoPay.js.map +1 -1
- package/build/src/hooks/useSolanaPaymentOptions.js +12 -45
- package/build/src/hooks/useSolanaPaymentOptions.js.map +1 -1
- package/build/src/hooks/useStellarPaymentOptions.js +10 -40
- package/build/src/hooks/useStellarPaymentOptions.js.map +1 -1
- package/build/src/hooks/useTokenOptions.js +3 -4
- package/build/src/hooks/useTokenOptions.js.map +1 -1
- package/build/src/hooks/useWalletPaymentOptions.js +21 -54
- package/build/src/hooks/useWalletPaymentOptions.js.map +1 -1
- package/build/src/payment/createPaymentPayload.js +13 -38
- package/build/src/payment/createPaymentPayload.js.map +1 -1
- package/build/src/payment/paymentEffects.js +5 -4
- package/build/src/payment/paymentEffects.js.map +1 -1
- package/build/src/payment/paymentFsm.js.map +1 -1
- package/build/src/utils/format.js +13 -82
- package/build/src/utils/format.js.map +1 -1
- package/build/src/utils/token.js +5 -21
- package/build/src/utils/token.js.map +1 -1
- package/build/src/utils/wallets.js +12 -0
- package/build/src/utils/wallets.js.map +1 -0
- package/build/utils/format.d.ts +3 -44
- package/build/utils/token.d.ts +2 -6
- package/package.json +2 -2
|
@@ -62,15 +62,14 @@ const fallbackOptions = [
|
|
|
62
62
|
* - Ethereum (Chain ID: 1) - Ethereum mainnet
|
|
63
63
|
* - BSC (Chain ID: 56) - Conditional support for MP app IDs
|
|
64
64
|
*/
|
|
65
|
-
function useDepositAddressOptions({ trpc, usdRequired,
|
|
65
|
+
function useDepositAddressOptions({ trpc, usdRequired, payParams, }) {
|
|
66
66
|
const { data, isLoading, error } = useQuery({
|
|
67
|
-
enabled: usdRequired != null && usdRequired > 0
|
|
68
|
-
queryKey: ["depositAddressOptions", usdRequired
|
|
67
|
+
enabled: usdRequired != null && usdRequired > 0,
|
|
68
|
+
queryKey: ["depositAddressOptions", usdRequired],
|
|
69
69
|
queryFn: async () => {
|
|
70
70
|
try {
|
|
71
71
|
return await trpc.getDepositAddressOptions.query({
|
|
72
|
-
usdRequired,
|
|
73
|
-
mode,
|
|
72
|
+
usdRequired: usdRequired,
|
|
74
73
|
});
|
|
75
74
|
}
|
|
76
75
|
catch (err) {
|
|
@@ -86,26 +85,17 @@ function useDepositAddressOptions({ trpc, usdRequired, mode, payParams, }) {
|
|
|
86
85
|
// Memoized configuration for deposit address options
|
|
87
86
|
const filteredOptions = useMemo(() => {
|
|
88
87
|
const options = data ?? [];
|
|
89
|
-
if (payParams?.preferredTokens && payParams
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// deposit options. Normalize per-chain: EVM lowercase, Solana/Stellar
|
|
94
|
-
// case-sensitive.
|
|
95
|
-
const normalize = (chainId, addr) => normalizeTokenAddress(chainId, addr) ?? addr;
|
|
96
|
-
const preferred = new Set(payParams.preferredTokens.map((pt) => `${pt.chainId}:${normalize(pt.chainId, pt.token)}`));
|
|
97
|
-
return options.filter((option) => preferred.has(`${option.token.chainId}:${normalize(option.token.chainId, option.token.token)}`));
|
|
88
|
+
if (payParams?.preferredTokens && payParams.preferredTokens.length > 0) {
|
|
89
|
+
return options.filter((option) => payParams.preferredTokens?.some((pt) => pt.token != null &&
|
|
90
|
+
normalizeTokenAddress(option.token.chainId, pt.token) ===
|
|
91
|
+
normalizeTokenAddress(option.token.chainId, option.token.token)));
|
|
98
92
|
}
|
|
99
93
|
return options;
|
|
100
94
|
}, [data, payParams?.preferredTokens]);
|
|
101
95
|
return {
|
|
102
96
|
options: filteredOptions,
|
|
103
97
|
loading: isLoading,
|
|
104
|
-
error: error
|
|
105
|
-
? error instanceof Error
|
|
106
|
-
? error.message
|
|
107
|
-
: "Failed to load deposit address options"
|
|
108
|
-
: null,
|
|
98
|
+
error: error ? String(error) : null,
|
|
109
99
|
};
|
|
110
100
|
}
|
|
111
101
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDepositAddressOptions.js","sources":["../../../src/hooks/useDepositAddressOptions.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"useDepositAddressOptions.js","sources":["../../../src/hooks/useDepositAddressOptions.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { ExternalPaymentOptions, rozoStellar, TokenSymbol, assert,
|
|
1
|
+
import { ExternalPaymentOptions, rozoStellar, TokenSymbol, assert, getPayment, checkoutPayment, buildCheckoutPayload, createPayment, debugJson, formatPaymentResponseToHydratedOrder, bscUSDT, assertNotNull, isValidSolanaAddress, rozoSolanaUSDC, rozoStellarUSDC, rozoStellarEURC, solana, rozoSolana, stellar, getChainById, getKnownToken, generateSolanaDeepLink, generateEVMDeepLink, writeRozoPayOrderID } from '@rozoai/intent-common';
|
|
2
2
|
import { useWallet, useConnection } from '@solana/wallet-adapter-react';
|
|
3
|
-
import { VersionedTransaction, PublicKey,
|
|
3
|
+
import { VersionedTransaction, PublicKey, Transaction } from '@solana/web3.js';
|
|
4
4
|
import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
|
|
5
|
-
import { getAddress, erc20Abi, hexToBytes
|
|
5
|
+
import { parseUnits, zeroAddress, getAddress, erc20Abi, hexToBytes } from 'viem';
|
|
6
6
|
import { useAccount, useSwitchChain, useSendTransaction, useWriteContract, useWalletClient, useCapabilities } from 'wagmi';
|
|
7
7
|
import { useWriteContracts } from 'wagmi/experimental';
|
|
8
|
-
import { tokenBaseAmountToDecimalString, tokenAmountToBaseUnits } from '../utils/format.js';
|
|
9
8
|
import { convertPreferredSymbolsToTokens } from '../utils/token.js';
|
|
10
9
|
import { createMemoInstruction } from '@solana/spl-memo';
|
|
11
10
|
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, TOKEN_PROGRAM_ID, createTransferCheckedInstruction } from '@solana/spl-token';
|
|
@@ -53,7 +52,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
53
52
|
const { data: walletClient } = useWalletClient();
|
|
54
53
|
const { data: walletCapabilities, isPending: capabilitiesPending } = useCapabilities();
|
|
55
54
|
const { writeContractsAsync } = useWriteContracts();
|
|
56
|
-
// order.metadata.dataSuffix survives ROZO_INVOICE_URL redirects where the
|
|
55
|
+
// ponytail: order.metadata.dataSuffix survives ROZO_INVOICE_URL redirects where the
|
|
57
56
|
// consumer's wagmi config (globalDataSuffix) is absent. Invoice checkout fetches the
|
|
58
57
|
// order first and passes metadata.dataSuffix into getDefaultConfig, but this fallback
|
|
59
58
|
// covers the in-SDK path where the order is already loaded.
|
|
@@ -162,26 +161,12 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
162
161
|
}
|
|
163
162
|
// payId mode: derive filtering params from the loaded order so that
|
|
164
163
|
// payment option hooks apply the same token filtering as appId mode.
|
|
165
|
-
// e.g. if destination is USDC,
|
|
166
|
-
// (we don't support payout to native tokens yet, but paying *with* native
|
|
167
|
-
// source balances is fine). EURC destinations are payable only with EURC.
|
|
164
|
+
// e.g. if destination is USDC, only show USDC/USDT sources (not EURC).
|
|
168
165
|
if (pay.order && pay.order.destFinalCallTokenAmount) {
|
|
169
166
|
const destSymbol = pay.order.destFinalCallTokenAmount.token.symbol;
|
|
170
|
-
// Derive preferredSymbol from destination: EURC destinations
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
// toChain/toToken and never native).
|
|
174
|
-
const preferredSymbol = destSymbol === TokenSymbol.EURC
|
|
175
|
-
? [TokenSymbol.EURC]
|
|
176
|
-
: [
|
|
177
|
-
TokenSymbol.USDC,
|
|
178
|
-
TokenSymbol.USDT,
|
|
179
|
-
TokenSymbol.ETH,
|
|
180
|
-
TokenSymbol.BNB,
|
|
181
|
-
TokenSymbol.POL,
|
|
182
|
-
TokenSymbol.SOL,
|
|
183
|
-
TokenSymbol.XLM,
|
|
184
|
-
];
|
|
167
|
+
// Derive preferredSymbol from destination: EURC destinations show EURC,
|
|
168
|
+
// everything else defaults to USDC + USDT.
|
|
169
|
+
const preferredSymbol = destSymbol === TokenSymbol.EURC ? [TokenSymbol.EURC] : [TokenSymbol.USDC, TokenSymbol.USDT];
|
|
185
170
|
const preferredTokens = convertPreferredSymbolsToTokens(preferredSymbol, undefined);
|
|
186
171
|
return {
|
|
187
172
|
toChain: pay.order.destFinalCallTokenAmount.token.chainId,
|
|
@@ -229,7 +214,6 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
229
214
|
const depositAddressOptions = useDepositAddressOptions({
|
|
230
215
|
trpc,
|
|
231
216
|
usdRequired,
|
|
232
|
-
mode: pay.order?.mode,
|
|
233
217
|
payParams: stablePayParams,
|
|
234
218
|
});
|
|
235
219
|
const chainOrderUsdLimits = useOrderUsdLimits({ trpc });
|
|
@@ -269,14 +253,15 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
269
253
|
// Read from ref instead of closure to get latest value and avoid stale state
|
|
270
254
|
const payParams = currPayParamsRef.current;
|
|
271
255
|
const order = pay.order;
|
|
256
|
+
// Narrow order to the specific shapes supported by buildCreatePaymentPayload.
|
|
257
|
+
// In error/idle states `pay.order` can be a plain RozoPayOrder or null,
|
|
258
|
+
// which is not assignable to the expected OrderLike type.
|
|
259
|
+
const orderForPayload = order && "org" in order
|
|
260
|
+
? order
|
|
261
|
+
: undefined;
|
|
272
262
|
// If payment already exists (payId mode or rozoPaymentId set), checkout instead of create.
|
|
273
|
-
// backend forbids `checkout` when rotating to a native source token
|
|
274
|
-
// (SOL/ETH/XLM) — "create a new order instead" — so route native sources to the
|
|
275
|
-
// create branch below instead of calling checkout.
|
|
276
263
|
const existingPayId = order?.externalId ?? rozoPaymentId ?? undefined;
|
|
277
|
-
|
|
278
|
-
const shouldCheckout = !!existingPayId && !rotateToNative;
|
|
279
|
-
if (!payParams || shouldCheckout) {
|
|
264
|
+
if (!payParams || existingPayId) {
|
|
280
265
|
if (!existingPayId) {
|
|
281
266
|
throw new Error("No pay params provided");
|
|
282
267
|
}
|
|
@@ -285,20 +270,11 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
285
270
|
if (!paymentRes?.data) {
|
|
286
271
|
throw new Error("Failed to fetch payment");
|
|
287
272
|
}
|
|
288
|
-
// Use required.amount (base units, already priced by the backend)
|
|
289
|
-
// converted to a human-readable token amount — NOT required.usd,
|
|
290
|
-
// which only equals the token amount for 1:1-USD-pegged tokens
|
|
291
|
-
// (USDC/USDT). For native ETH/BNB/POL/SOL/XLM this would send the
|
|
292
|
-
// wrong amount to the backend (e.g. "1.00" instead of "0.0005").
|
|
293
|
-
// Some endpoints return required.amount as an already-human-readable
|
|
294
|
-
// decimal string instead of integer base units, so detect the shape
|
|
295
|
-
// first — BigInt() throws on decimal strings.
|
|
296
|
-
const sourceAmount = tokenBaseAmountToDecimalString(walletOption.required.amount, walletOption.required.token.decimals);
|
|
297
273
|
const checkoutRes = await checkoutPayment(existingPayId, buildCheckoutPayload(paymentRes.data, {
|
|
298
274
|
chainId: walletOption.required.token.chainId,
|
|
299
275
|
tokenSymbol: walletOption.required.token.symbol,
|
|
300
276
|
tokenAddress: walletOption.required.token.token,
|
|
301
|
-
amount:
|
|
277
|
+
amount: String(walletOption.required.usd),
|
|
302
278
|
}));
|
|
303
279
|
if (!checkoutRes?.data) {
|
|
304
280
|
throw new Error("Failed to checkout payment");
|
|
@@ -317,14 +293,6 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
317
293
|
}
|
|
318
294
|
}
|
|
319
295
|
try {
|
|
320
|
-
// Narrow order to the specific shapes supported by buildCreatePaymentPayload.
|
|
321
|
-
// In error/idle states `pay.order` can be a plain RozoPayOrder or null,
|
|
322
|
-
// which lacks `destFinalCallTokenAmount`; passing it through would throw
|
|
323
|
-
// when we derive the destination amount. Fall back to the payParams-only
|
|
324
|
-
// branch (order undefined) in that case.
|
|
325
|
-
const orderForPayload = order && "org" in order
|
|
326
|
-
? order
|
|
327
|
-
: undefined;
|
|
328
296
|
const payload = buildCreatePaymentPayload({
|
|
329
297
|
payParams,
|
|
330
298
|
order: orderForPayload,
|
|
@@ -365,11 +333,9 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
365
333
|
if (!validatePaymentRequirements(walletOption, pay.paymentState)) {
|
|
366
334
|
throw new Error(`[PAY TOKEN] required token ${debugJson(required)} does not match fees token ${debugJson(fees)}`);
|
|
367
335
|
}
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
// amount (e.g. 1.00 ETH instead of ~0.0006 ETH for a $1 payment).
|
|
372
|
-
const paymentAmount = tokenAmountToBaseUnits(required.amount, required.token.decimals);
|
|
336
|
+
// @NOTE: Fee handled by Rozo API
|
|
337
|
+
// const paymentAmount = BigInt(required.amount) + BigInt(fees.amount);
|
|
338
|
+
const paymentAmount = parseUnits(required.usd.toString(), required.token.decimals);
|
|
373
339
|
// Check if we need to create a new Rozo payment (cache this check)
|
|
374
340
|
const previousChainId = pay.order.preferredChainId ? Number(pay.order.preferredChainId) : null;
|
|
375
341
|
// If we have an existing payment and chain differs, MUST create new payment
|
|
@@ -381,11 +347,8 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
381
347
|
pay.order.destFinalCallTokenAmount?.token?.chainId !== required.token.chainId);
|
|
382
348
|
log?.(`[PAY TOKEN] Chain check: previous=${previousChainId}, current=${required.token.chainId}, hasExistingPayment=${hasExistingPayment}, needRozoPayment=${needRozoPayment}`);
|
|
383
349
|
// Prepare transaction parameters early (before async operations)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// would misclassify native ETH as an ERC20 and take the wrong transfer path.
|
|
387
|
-
const isNative = isNativeToken(required.token.token);
|
|
388
|
-
const tokenAddress = isNative ? null : getAddress(required.token.token);
|
|
350
|
+
const isNativeToken = required.token.token === zeroAddress;
|
|
351
|
+
const tokenAddress = isNativeToken ? null : getAddress(required.token.token);
|
|
389
352
|
// Get hydrated order efficiently with parallel preparation
|
|
390
353
|
let hydratedOrder;
|
|
391
354
|
let paymentId;
|
|
@@ -482,14 +445,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
482
445
|
// Execute transaction with optimized error handling
|
|
483
446
|
const paymentTxHash = await (async () => {
|
|
484
447
|
try {
|
|
485
|
-
if (
|
|
486
|
-
await switchChainAsync({ chainId: required.token.chainId });
|
|
487
|
-
}
|
|
488
|
-
log?.(`[PAY ERC20] dataSuffix: ${resolvedDataSuffix ?? "(none)"}`);
|
|
489
|
-
// EIP-5792 path: only when wallet advertises dataSuffix capability (Base App / Coinbase Wallet).
|
|
490
|
-
const chainCapabilities = walletCapabilities?.[required.token.chainId];
|
|
491
|
-
const supportsDataSuffix = !capabilitiesPending && resolvedDataSuffix != null && !!chainCapabilities?.dataSuffix;
|
|
492
|
-
if (isNative) {
|
|
448
|
+
if (isNativeToken) {
|
|
493
449
|
// dataSuffix intentionally omitted — appending data to bare ETH transfers
|
|
494
450
|
// changes wallet UI display; builder-code attribution targets contract calls.
|
|
495
451
|
return await sendTransactionAsync({
|
|
@@ -498,6 +454,13 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
498
454
|
});
|
|
499
455
|
}
|
|
500
456
|
else {
|
|
457
|
+
if (required.token.chainId !== bscUSDT.chainId) {
|
|
458
|
+
await switchChainAsync({ chainId: required.token.chainId });
|
|
459
|
+
}
|
|
460
|
+
log?.(`[PAY ERC20] dataSuffix: ${resolvedDataSuffix ?? "(none)"}`);
|
|
461
|
+
// EIP-5792 path: only when wallet advertises dataSuffix capability (Base App / Coinbase Wallet).
|
|
462
|
+
const chainCapabilities = walletCapabilities?.[required.token.chainId];
|
|
463
|
+
const supportsDataSuffix = !capabilitiesPending && resolvedDataSuffix != null && !!chainCapabilities?.dataSuffix;
|
|
501
464
|
if (supportsDataSuffix) {
|
|
502
465
|
if (!walletClient)
|
|
503
466
|
throw new Error("No walletClient available");
|
|
@@ -630,9 +593,11 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
630
593
|
throw new Error(`Invalid Solana destination address format: ${rozoPayment.destAddress}`);
|
|
631
594
|
}
|
|
632
595
|
// Set up token addresses
|
|
596
|
+
let mintAddress;
|
|
633
597
|
let fromKey;
|
|
634
598
|
let toKey;
|
|
635
599
|
try {
|
|
600
|
+
mintAddress = new PublicKey(walletPaymentOption.required.token.token);
|
|
636
601
|
fromKey = new PublicKey(payerPublicKey);
|
|
637
602
|
toKey = new PublicKey(rozoPayment.destAddress);
|
|
638
603
|
}
|
|
@@ -640,77 +605,32 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
640
605
|
throw new Error(`Invalid Solana address format: ${error.message}. ` +
|
|
641
606
|
`Destination address: ${rozoPayment.destAddress}`);
|
|
642
607
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
log("[PAY SOLANA] Transfer amount (lamports):", lamports.toString());
|
|
664
|
-
instructions.push(SystemProgram.transfer({
|
|
665
|
-
fromPubkey: fromKey,
|
|
666
|
-
toPubkey: toKey,
|
|
667
|
-
lamports: parseUnits(rozoPayment.sourceAmount, solanaSOL.decimals),
|
|
668
|
-
}));
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
let mintAddress;
|
|
672
|
-
try {
|
|
673
|
-
mintAddress = new PublicKey(walletPaymentOption.required.token.token);
|
|
674
|
-
}
|
|
675
|
-
catch (error) {
|
|
676
|
-
throw new Error(`Invalid Solana address format: ${error.message}. ` +
|
|
677
|
-
`Destination address: ${rozoPayment.destAddress}`);
|
|
678
|
-
}
|
|
679
|
-
log("[PAY SOLANA] Transaction details:", {
|
|
680
|
-
tokenMint: mintAddress.toString(),
|
|
681
|
-
fromKey: fromKey.toString(),
|
|
682
|
-
toKey: toKey.toString(),
|
|
683
|
-
amount: walletPaymentOption.required.amount,
|
|
684
|
-
usd: walletPaymentOption.required.usd,
|
|
685
|
-
memo: rozoPayment.memo,
|
|
686
|
-
});
|
|
687
|
-
// Get token accounts for sender and recipient
|
|
688
|
-
log("[PAY SOLANA] Deriving associated token accounts...");
|
|
689
|
-
const senderTokenAccount = await getAssociatedTokenAddress(mintAddress, fromKey);
|
|
690
|
-
const recipientTokenAccount = await getAssociatedTokenAddress(mintAddress, toKey);
|
|
691
|
-
log("[PAY SOLANA] Sender token account:", senderTokenAccount.toString());
|
|
692
|
-
log("[PAY SOLANA] Recipient token account:", recipientTokenAccount.toString());
|
|
693
|
-
// Check if recipient token account exists
|
|
694
|
-
log("[PAY SOLANA] Checking if recipient token account exists...");
|
|
695
|
-
const recipientTokenInfo = await connection.getAccountInfo(recipientTokenAccount);
|
|
696
|
-
// Create recipient token account if it doesn't exist
|
|
697
|
-
if (!recipientTokenInfo) {
|
|
698
|
-
log("[PAY SOLANA] Creating recipient token account...");
|
|
699
|
-
instructions.push(createAssociatedTokenAccountInstruction(payerPublicKey, recipientTokenAccount, toKey, mintAddress, TOKEN_PROGRAM_ID));
|
|
700
|
-
}
|
|
701
|
-
// Add transfer instruction
|
|
702
|
-
// Use required.amount (already computed by the backend in the
|
|
703
|
-
// token's base units, correctly priced from USD) instead of
|
|
704
|
-
// re-deriving from required.usd with a hardcoded 6-decimal
|
|
705
|
-
// assumption, which silently breaks for non-1:1-USD or
|
|
706
|
-
// differently-decimaled SPL tokens.
|
|
707
|
-
log("[PAY SOLANA] Adding transfer instruction...");
|
|
708
|
-
// Keep as bigint — createTransferCheckedInstruction accepts number | bigint.
|
|
709
|
-
// Converting to Number would lose precision for amounts > 2^53 base units.
|
|
710
|
-
const transferAmount = tokenAmountToBaseUnits(walletPaymentOption.required.amount, walletPaymentOption.required.token.decimals);
|
|
711
|
-
log("[PAY SOLANA] Transfer amount (base units):", transferAmount.toString());
|
|
712
|
-
instructions.push(createTransferCheckedInstruction(senderTokenAccount, mintAddress, recipientTokenAccount, fromKey, transferAmount, walletPaymentOption.required.token.decimals));
|
|
608
|
+
log("[PAY SOLANA] Transaction details:", {
|
|
609
|
+
tokenMint: mintAddress.toString(),
|
|
610
|
+
fromKey: fromKey.toString(),
|
|
611
|
+
toKey: toKey.toString(),
|
|
612
|
+
amount: walletPaymentOption.required.usd,
|
|
613
|
+
memo: rozoPayment.memo,
|
|
614
|
+
});
|
|
615
|
+
// Get token accounts for sender and recipient
|
|
616
|
+
log("[PAY SOLANA] Deriving associated token accounts...");
|
|
617
|
+
const senderTokenAccount = await getAssociatedTokenAddress(mintAddress, fromKey);
|
|
618
|
+
const recipientTokenAccount = await getAssociatedTokenAddress(mintAddress, toKey);
|
|
619
|
+
log("[PAY SOLANA] Sender token account:", senderTokenAccount.toString());
|
|
620
|
+
log("[PAY SOLANA] Recipient token account:", recipientTokenAccount.toString());
|
|
621
|
+
// Check if recipient token account exists
|
|
622
|
+
log("[PAY SOLANA] Checking if recipient token account exists...");
|
|
623
|
+
const recipientTokenInfo = await connection.getAccountInfo(recipientTokenAccount);
|
|
624
|
+
// Create recipient token account if it doesn't exist
|
|
625
|
+
if (!recipientTokenInfo) {
|
|
626
|
+
log("[PAY SOLANA] Creating recipient token account...");
|
|
627
|
+
instructions.push(createAssociatedTokenAccountInstruction(payerPublicKey, recipientTokenAccount, toKey, mintAddress, TOKEN_PROGRAM_ID));
|
|
713
628
|
}
|
|
629
|
+
// Add transfer instruction
|
|
630
|
+
log("[PAY SOLANA] Adding transfer instruction...");
|
|
631
|
+
const transferAmount = parseFloat(walletPaymentOption.required.usd.toString()) * 1_000_000;
|
|
632
|
+
log("[PAY SOLANA] Transfer amount (with decimals):", transferAmount);
|
|
633
|
+
instructions.push(createTransferCheckedInstruction(senderTokenAccount, mintAddress, recipientTokenAccount, fromKey, transferAmount, rozoSolanaUSDC.decimals));
|
|
714
634
|
// Add memo if provided
|
|
715
635
|
if (rozoPayment.memo) {
|
|
716
636
|
log("[PAY SOLANA] Adding memo instruction:", rozoPayment.memo);
|
|
@@ -773,29 +693,18 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
773
693
|
await setStellarWallet(stellarConnector);
|
|
774
694
|
}
|
|
775
695
|
const sourceAccount = await stellarServer.loadAccount(stellarPublicKey);
|
|
776
|
-
let
|
|
777
|
-
if (walletPaymentOption.required.token.token ===
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
else if (walletPaymentOption.required.token.token === rozoStellarUSDC.token) {
|
|
781
|
-
destAsset = new Asset(walletPaymentOption.required.token.symbol, rozoStellarUSDC.token.split(":")[1]);
|
|
696
|
+
let issuer = "";
|
|
697
|
+
if (walletPaymentOption.required.token.token === rozoStellarUSDC.token) {
|
|
698
|
+
issuer = rozoStellarUSDC.token.split(":")[1];
|
|
782
699
|
}
|
|
783
700
|
else if (walletPaymentOption.required.token.token === rozoStellarEURC.token) {
|
|
784
|
-
|
|
701
|
+
issuer = rozoStellarEURC.token.split(":")[1];
|
|
785
702
|
}
|
|
786
703
|
else {
|
|
787
704
|
throw new Error("Unsupported token");
|
|
788
705
|
}
|
|
706
|
+
const destAsset = new Asset(walletPaymentOption.required.token.symbol, issuer);
|
|
789
707
|
const fee = String(await stellarServer.fetchBaseFee());
|
|
790
|
-
// Use required.amount (base units, already priced by the backend)
|
|
791
|
-
// converted to a human-readable token amount — NOT required.usd,
|
|
792
|
-
// which only equals the token amount for 1:1-USD-pegged tokens
|
|
793
|
-
// (USDC/EURC). For native XLM this would send the wrong amount
|
|
794
|
-
// (e.g. "1.00" XLM instead of the correct ~2.7 XLM for a $1 payment).
|
|
795
|
-
// Some endpoints return required.amount as an already-human-readable
|
|
796
|
-
// decimal string instead of integer base units, so detect the shape
|
|
797
|
-
// first — BigInt() throws on decimal strings.
|
|
798
|
-
const paymentAmount = tokenBaseAmountToDecimalString(walletPaymentOption.required.amount, walletPaymentOption.required.token.decimals);
|
|
799
708
|
// Build transaction
|
|
800
709
|
const transaction = new TransactionBuilder(sourceAccount, {
|
|
801
710
|
fee,
|
|
@@ -804,7 +713,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
804
713
|
.addOperation(Operation.payment({
|
|
805
714
|
destination: destinationAddress,
|
|
806
715
|
asset: destAsset,
|
|
807
|
-
amount:
|
|
716
|
+
amount: String(walletPaymentOption.required.usd),
|
|
808
717
|
}))
|
|
809
718
|
.setTimeout(180);
|
|
810
719
|
if (rozoPayment.memo) {
|
|
@@ -879,29 +788,17 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
879
788
|
}
|
|
880
789
|
else {
|
|
881
790
|
log?.("[PAY DEPOSIT ADDRESS] hydrating order");
|
|
882
|
-
// hydrateOrder needs required.amount so buildCreatePaymentPayload
|
|
883
|
-
// can set preferredAmountUnits. For BOTH native (SOL/ETH/XLM) and
|
|
884
|
-
// 1:1-USD-pegged sources, the amount must come from the fee response
|
|
885
|
-
// (BE-computed source amount in source units, fee-inclusive) — NOT the
|
|
886
|
-
// destination USD value. Otherwise tokenBaseAmountToDecimalString
|
|
887
|
-
// formats the toUnits through the source decimals and the BE rejects
|
|
888
|
-
// the underflowing pay-in (e.g. "10" with 6 decimals → "0.00001").
|
|
889
|
-
const isNativeSource = isNativeToken(option.token.token);
|
|
890
|
-
const sourceAmountUnits = String(fees?.source?.amount ??
|
|
891
|
-
(isNativeSource ? "" : (pay.order?.destFinalCallTokenAmount?.usd ?? "0")));
|
|
892
|
-
if (isNativeSource && sourceAmountUnits === "") {
|
|
893
|
-
throw new Error("Fee source amount required for native token deposit");
|
|
894
|
-
}
|
|
895
|
-
const actualFeeType = payParams?.feeType ?? FeeType.ExactIn;
|
|
896
791
|
const result = await pay.hydrateOrder(undefined, {
|
|
897
792
|
required: {
|
|
898
|
-
|
|
899
|
-
|
|
793
|
+
token: {
|
|
794
|
+
token: option.token.token,
|
|
795
|
+
chainId: option.token.chainId,
|
|
796
|
+
},
|
|
900
797
|
},
|
|
901
798
|
fees: {
|
|
902
799
|
usd: fees?.source?.fee != null ? parseFloat(fees.source.fee) : 0,
|
|
903
800
|
},
|
|
904
|
-
}
|
|
801
|
+
});
|
|
905
802
|
order = result.order;
|
|
906
803
|
}
|
|
907
804
|
log?.(`[PAY DEPOSIT ADDRESS] hydrated order ${order.id} for ${order.usdValue} USD, checking out with deposit address: ${option}`);
|
|
@@ -918,23 +815,12 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
918
815
|
if (!preferredToken) {
|
|
919
816
|
throw new Error("Preferred token not found");
|
|
920
817
|
}
|
|
921
|
-
// Amount the user must send, in source-token units. Prefer the backend's
|
|
922
|
-
// source.amount (surfaced via metadata by formatPaymentResponseToHydratedOrder)
|
|
923
|
-
// over usdValue: for native sources (SOL/ETH/XLM) the source amount differs
|
|
924
|
-
// from the USD/destination value, so usdValue would show e.g. "1.3 SOL"
|
|
925
|
-
// instead of the correct ~0.0169 SOL. For 1:1-USD sources they coincide.
|
|
926
|
-
const isNativeDepositSource = isNativeToken(option.token.token);
|
|
927
|
-
const metaSourceAmount = order.metadata?.sourceAmountUnits;
|
|
928
|
-
if (isNativeDepositSource && metaSourceAmount == null) {
|
|
929
|
-
throw new Error("sourceAmountUnits missing on hydrated order for native deposit source");
|
|
930
|
-
}
|
|
931
|
-
const sourceAmountUnits = metaSourceAmount != null ? String(metaSourceAmount) : String(order.usdValue);
|
|
932
818
|
let uriDeeplink = null;
|
|
933
819
|
// Use Solana deep link if it's a Solana chain.
|
|
934
820
|
// Solana pay-in no longer requires a memo.
|
|
935
821
|
if ([solana.chainId, rozoSolana.chainId].includes(preferredToken.chainId)) {
|
|
936
822
|
uriDeeplink = generateSolanaDeepLink({
|
|
937
|
-
amountUnits:
|
|
823
|
+
amountUnits: order.destFinalCallTokenAmount.usd.toString(),
|
|
938
824
|
recipientAddress: order.intentAddr,
|
|
939
825
|
tokenAddress: preferredToken.token,
|
|
940
826
|
});
|
|
@@ -946,7 +832,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
946
832
|
// Otherwise use EVM deep link
|
|
947
833
|
else {
|
|
948
834
|
uriDeeplink = generateEVMDeepLink({
|
|
949
|
-
amountUnits: parseUnits(
|
|
835
|
+
amountUnits: parseUnits(order.destFinalCallTokenAmount.usd.toString(), preferredToken.decimals).toString(),
|
|
950
836
|
chainId: preferredToken.chainId,
|
|
951
837
|
recipientAddress: order.intentAddr,
|
|
952
838
|
tokenAddress: preferredToken.token,
|
|
@@ -954,7 +840,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
954
840
|
}
|
|
955
841
|
return {
|
|
956
842
|
address: order.intentAddr,
|
|
957
|
-
amount:
|
|
843
|
+
amount: String(order.usdValue),
|
|
958
844
|
suffix: `${option.token.symbol} ${chain.name}`,
|
|
959
845
|
uri: uriDeeplink ?? "",
|
|
960
846
|
expirationS: Math.floor(Date.now() / 1000) + 300,
|
|
@@ -969,7 +855,7 @@ function usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl
|
|
|
969
855
|
order: pay.order,
|
|
970
856
|
message,
|
|
971
857
|
});
|
|
972
|
-
return
|
|
858
|
+
return null;
|
|
973
859
|
}
|
|
974
860
|
finally {
|
|
975
861
|
// Remove from processing set when done (allow retries after completion/failure)
|