@rozoai/intent-common 0.0.28-beta.2 → 0.0.28-beta.3

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/src/bridge.ts ADDED
@@ -0,0 +1,382 @@
1
+ import { parseUnits } from "viem";
2
+ import {
3
+ base,
4
+ baseUSDC,
5
+ bscUSDT,
6
+ getKnownToken,
7
+ polygonUSDC,
8
+ RozoPayHydratedOrderWithOrg,
9
+ RozoPayIntentStatus,
10
+ RozoPayOrderMode,
11
+ RozoPayOrderStatusDest,
12
+ RozoPayOrderStatusSource,
13
+ RozoPayUserMetadata,
14
+ rozoSolanaUSDC,
15
+ rozoStellar,
16
+ rozoStellarUSDC,
17
+ } from ".";
18
+ import type { PaymentResponseData } from "./api";
19
+
20
+ export interface PaymentBridgeConfig {
21
+ toChain?: number;
22
+ toToken?: string;
23
+ toAddress?: string;
24
+ toStellarAddress?: string;
25
+ toSolanaAddress?: string;
26
+ toUnits: string;
27
+ payInTokenAddress: string;
28
+ log?: (msg: string) => void;
29
+ }
30
+
31
+ export interface PreferredPaymentConfig {
32
+ preferredChain: string;
33
+ preferredToken: "USDC" | "USDT" | "XLM";
34
+ preferredTokenAddress?: string;
35
+ }
36
+
37
+ export interface DestinationConfig {
38
+ destinationAddress?: string;
39
+ chainId: string;
40
+ amountUnits: string;
41
+ tokenSymbol: string;
42
+ tokenAddress: string;
43
+ }
44
+
45
+ /**
46
+ * Creates payment bridge configuration for cross-chain payment routing
47
+ *
48
+ * Determines the optimal payment routing based on the destination chain/token
49
+ * and the wallet payment option selected by the user. This function handles
50
+ * the complexity of multi-chain payments by:
51
+ *
52
+ * 1. **Preferred Payment Method**: Identifies which chain/token the user will pay from
53
+ * - Supports Base USDC, Polygon USDC, Solana USDC, Stellar USDC, and BSC USDT
54
+ * - Sets appropriate chain ID and token address for the source transaction
55
+ *
56
+ * 2. **Destination Configuration**: Determines where funds will be received
57
+ * - Supports Base, Solana, and Stellar as destination chains
58
+ * - Handles special address formats for Solana and Stellar addresses
59
+ * - Defaults to Base USDC when no special destination is specified
60
+ *
61
+ * 3. **Cross-Chain Bridging**: Configures the payment bridge parameters
62
+ * - Maps user's selected wallet/token to the appropriate payment method
63
+ * - Sets up destination chain and token configuration
64
+ * - Handles token address formatting (e.g., Stellar's `USDC:issuerPK` format)
65
+ *
66
+ * @param config - Payment bridge configuration parameters
67
+ * @param config.toChain - Destination chain ID (defaults to Base: 8453)
68
+ * @param config.toToken - Destination token address (defaults to Base USDC)
69
+ * @param config.toAddress - Standard EVM destination address
70
+ * @param config.toStellarAddress - Stellar-specific destination address (if paying to Stellar)
71
+ * @param config.toSolanaAddress - Solana-specific destination address (if paying to Solana)
72
+ * @param config.toUnits - Amount in token units (smallest denomination)
73
+ * @param config.payInTokenAddress - Token address user selected to pay with
74
+ * @param config.log - Optional logging function for debugging
75
+ *
76
+ * @returns Payment routing configuration
77
+ * @returns preferred - Source payment configuration (chain, token user will pay from)
78
+ * @returns destination - Destination payment configuration (chain, token user will receive)
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // User wants to pay with Polygon USDC to receive on Base
83
+ * const { preferred, destination } = createPaymentBridgeConfig({
84
+ * toChain: 8453, // Base
85
+ * toToken: baseUSDC.token,
86
+ * toAddress: '0x123...',
87
+ * toUnits: '1000000', // 1 USDC
88
+ * payInTokenAddress: polygonUSDC.token,
89
+ * log: console.log
90
+ * });
91
+ *
92
+ * // preferred = { preferredChain: '137', preferredToken: 'USDC', preferredTokenAddress: '0x2791...' }
93
+ * // destination = { destinationAddress: '0x123...', chainId: '8453', amountUnits: '1000000', ... }
94
+ * ```
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * // User wants to pay to a Stellar address
99
+ * const { preferred, destination } = createPaymentBridgeConfig({
100
+ * toStellarAddress: 'GDZS...',
101
+ * toUnits: '1000000',
102
+ * payInTokenAddress: baseUSDC.token,
103
+ * });
104
+ *
105
+ * // destination will be configured for Stellar chain with USDC:issuerPK format
106
+ * ```
107
+ *
108
+ * @note Currently only supports Base USDC and Stellar USDC as destination chains.
109
+ * Support for additional destination chains is planned.
110
+ *
111
+ * @see PreferredPaymentConfig
112
+ * @see DestinationConfig
113
+ */
114
+ export function createPaymentBridgeConfig({
115
+ toChain = baseUSDC.chainId,
116
+ toToken = baseUSDC.token,
117
+ toAddress,
118
+ toStellarAddress,
119
+ toSolanaAddress,
120
+ toUnits,
121
+ payInTokenAddress,
122
+ log,
123
+ }: PaymentBridgeConfig): {
124
+ preferred: PreferredPaymentConfig;
125
+ destination: DestinationConfig;
126
+ } {
127
+ // Default configuration for Base USDC payments
128
+ let preferred: PreferredPaymentConfig = {
129
+ preferredChain: String(toChain),
130
+ preferredToken: "USDC",
131
+ };
132
+
133
+ let destination: DestinationConfig = {
134
+ destinationAddress: toAddress,
135
+ chainId: String(toChain),
136
+ amountUnits: toUnits,
137
+ tokenSymbol: "USDC",
138
+ tokenAddress: toToken,
139
+ };
140
+
141
+ /**
142
+ * IMPORTANT: Because we only support PAY OUT USDC BASE & STELLAR
143
+ * So, We force toChain and toToken to Base USDC as default PayParams
144
+ *
145
+ * @TODO: Adjust this when we support another PAY OUT chain
146
+ */
147
+ if (toChain === base.chainId && toToken === baseUSDC.token) {
148
+ // Determine preferred payment method based on wallet selection
149
+ if (payInTokenAddress) {
150
+ // Pay In USDC Polygon
151
+ if (payInTokenAddress === polygonUSDC.token) {
152
+ log?.(`[Payment Bridge] Pay In USDC Polygon`);
153
+ preferred = {
154
+ preferredChain: String(polygonUSDC.chainId),
155
+ preferredToken: "USDC",
156
+ preferredTokenAddress: polygonUSDC.token,
157
+ };
158
+ }
159
+ // Pay In USDC Solana
160
+ else if (payInTokenAddress === rozoSolanaUSDC.token) {
161
+ log?.(`[Payment Bridge] Pay In USDC Solana`);
162
+ preferred = {
163
+ preferredChain: String(rozoSolanaUSDC.chainId),
164
+ preferredToken: "USDC",
165
+ preferredTokenAddress: rozoSolanaUSDC.token,
166
+ };
167
+ }
168
+ // Pay In USDC Stellar
169
+ else if (payInTokenAddress === rozoStellarUSDC.token) {
170
+ log?.(`[Payment Bridge] Pay In USDC Stellar`);
171
+ preferred = {
172
+ preferredChain: String(rozoStellarUSDC.chainId),
173
+ preferredToken: "USDC",
174
+ preferredTokenAddress: `USDC:${rozoStellarUSDC.token}`,
175
+ };
176
+ }
177
+ // Pay In USDT BSC
178
+ else if (payInTokenAddress === bscUSDT.token) {
179
+ log?.(`[Payment Bridge] Pay In USDT BSC`);
180
+ preferred = {
181
+ preferredChain: String(bscUSDT.chainId),
182
+ preferredToken: "USDT",
183
+ preferredTokenAddress: bscUSDT.token,
184
+ };
185
+ }
186
+ }
187
+
188
+ // Determine destination based on special address types
189
+ if (toStellarAddress) {
190
+ log?.(`[Payment Bridge] Pay Out USDC Stellar`);
191
+ destination = {
192
+ destinationAddress: toStellarAddress,
193
+ chainId: String(rozoStellar.chainId),
194
+ amountUnits: toUnits,
195
+ tokenSymbol: "USDC",
196
+ tokenAddress: `USDC:${rozoStellarUSDC.token}`,
197
+ };
198
+ } else if (toSolanaAddress) {
199
+ log?.(`[Payment Bridge] Pay Out USDC Solana`);
200
+ destination = {
201
+ destinationAddress: toSolanaAddress,
202
+ chainId: String(rozoSolanaUSDC.chainId),
203
+ amountUnits: toUnits,
204
+ tokenSymbol: "USDC",
205
+ tokenAddress: rozoSolanaUSDC.token,
206
+ };
207
+ } else {
208
+ log?.(`[Payment Bridge] Pay Out USDC Base`);
209
+ // Keep default Base configuration
210
+ }
211
+ }
212
+
213
+ return { preferred, destination };
214
+ }
215
+
216
+ /**
217
+ * Transforms a payment API response into a fully hydrated order object
218
+ *
219
+ * Converts the payment response data from the RozoAI payment API into a complete
220
+ * `RozoPayHydratedOrderWithOrg` object that contains all the information needed
221
+ * to display order status, track payments, and handle cross-chain transactions.
222
+ *
223
+ * This function performs several key transformations:
224
+ *
225
+ * 1. **Token Resolution**: Identifies the correct token based on chain and address
226
+ * - Uses `getKnownToken()` to resolve token metadata (decimals, symbol, logo, etc.)
227
+ * - Handles special cases for Stellar tokens using issuer public key
228
+ *
229
+ * 2. **Order Structure**: Creates a complete order with all required fields
230
+ * - Generates random order ID for internal tracking
231
+ * - Sets up intent, handoff, and contract addresses
232
+ * - Configures bridge token options and destination amounts
233
+ *
234
+ * 3. **Status Initialization**: Sets initial payment statuses
235
+ * - Source status: WAITING_PAYMENT (awaiting user transaction)
236
+ * - Destination status: PENDING (not yet received)
237
+ * - Intent status: UNPAID (payment not initiated)
238
+ *
239
+ * 4. **Metadata Merge**: Combines various metadata sources
240
+ * - Merges order metadata, user metadata, and custom metadata
241
+ * - Preserves external ID and organization information
242
+ *
243
+ * @param order - Payment response data from the RozoAI payment API
244
+ * @param order.metadata - Payment metadata including chain, token, and routing info
245
+ * @param order.destination - Destination configuration (chain, token, amount, address)
246
+ * @param order.source - Source transaction info (if payment has been initiated)
247
+ * @param order.orgId - Organization ID for the payment
248
+ * @param order.externalId - External reference ID (if provided by merchant)
249
+ *
250
+ * @returns Complete hydrated order object with all payment tracking information
251
+ * @returns id - Unique order identifier (random BigInt)
252
+ * @returns mode - Order mode (HYDRATED)
253
+ * @returns sourceStatus - Source transaction status
254
+ * @returns destStatus - Destination transaction status
255
+ * @returns intentStatus - Overall payment intent status
256
+ * @returns metadata - Merged metadata from all sources
257
+ * @returns org - Organization information
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * const paymentResponse = await getRozoPayment(paymentId);
262
+ *
263
+ * const hydratedOrder = formatPaymentResponseDataToHydratedOrder(
264
+ * paymentResponse.data,
265
+ * 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN'
266
+ * );
267
+ *
268
+ * console.log(hydratedOrder.sourceStatus); // 'WAITING_PAYMENT'
269
+ * console.log(hydratedOrder.destFinalCallTokenAmount.token.symbol); // 'USDC'
270
+ * console.log(hydratedOrder.usdValue); // 10.00
271
+ * ```
272
+ *
273
+ * @note The generated order ID is random and intended for client-side tracking only.
274
+ * Use `externalId` or the API payment ID for server-side reference.
275
+ *
276
+ * @note The function sets a 5-minute expiration timestamp from the current time.
277
+ *
278
+ * @see PaymentResponseData
279
+ * @see RozoPayHydratedOrderWithOrg
280
+ * @see getKnownToken
281
+ */
282
+ export function formatResponseToHydratedOrder(
283
+ order: PaymentResponseData
284
+ ): RozoPayHydratedOrderWithOrg {
285
+ const destAddress = order.metadata.receivingAddress as `0x${string}`;
286
+
287
+ const requiredChain = order.metadata.preferredChain || baseUSDC.chainId;
288
+
289
+ const chain = getKnownToken(
290
+ Number(requiredChain),
291
+ Number(requiredChain) === rozoStellar.chainId
292
+ ? rozoStellarUSDC.token
293
+ : order.metadata.preferredTokenAddress
294
+ );
295
+
296
+ return {
297
+ id: BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)),
298
+ mode: RozoPayOrderMode.HYDRATED,
299
+ intentAddr: destAddress,
300
+ handoffAddr: destAddress,
301
+ escrowContractAddress: destAddress,
302
+ bridgerContractAddress: destAddress,
303
+ // @TODO: use correct destination token
304
+ bridgeTokenOutOptions: [
305
+ {
306
+ token: {
307
+ chainId: baseUSDC.chainId,
308
+ token: baseUSDC.token,
309
+ symbol: baseUSDC.symbol,
310
+ usd: 1,
311
+ priceFromUsd: 1,
312
+ decimals: baseUSDC.decimals,
313
+ displayDecimals: 2,
314
+ logoSourceURI: baseUSDC.logoSourceURI,
315
+ logoURI: baseUSDC.logoURI,
316
+ maxAcceptUsd: 100000,
317
+ maxSendUsd: 0,
318
+ },
319
+ amount: parseUnits(
320
+ order.destination.amountUnits,
321
+ baseUSDC.decimals
322
+ ).toString() as `${bigint}`,
323
+ usd: Number(order.destination.amountUnits),
324
+ },
325
+ ],
326
+ selectedBridgeTokenOutAddr: null,
327
+ selectedBridgeTokenOutAmount: null,
328
+ destFinalCallTokenAmount: {
329
+ token: {
330
+ chainId: chain ? chain.chainId : baseUSDC.chainId,
331
+ token: chain ? chain.token : baseUSDC.token,
332
+ symbol: chain ? chain.symbol : baseUSDC.symbol,
333
+ usd: 1,
334
+ priceFromUsd: 1,
335
+ decimals: chain ? chain.decimals : baseUSDC.decimals,
336
+ displayDecimals: 2,
337
+ logoSourceURI: chain ? chain.logoSourceURI : baseUSDC.logoSourceURI,
338
+ logoURI: chain ? chain.logoURI : baseUSDC.logoURI,
339
+ maxAcceptUsd: 100000,
340
+ maxSendUsd: 0,
341
+ },
342
+ amount: parseUnits(
343
+ order.destination.amountUnits,
344
+ chain ? chain.decimals : baseUSDC.decimals
345
+ ).toString() as `${bigint}`,
346
+ usd: Number(order.destination.amountUnits),
347
+ },
348
+ usdValue: Number(order.destination.amountUnits),
349
+ destFinalCall: {
350
+ to: destAddress,
351
+ value: BigInt("0"),
352
+ data: "0x",
353
+ },
354
+ refundAddr: (order.source?.sourceAddress as `0x${string}`) || null,
355
+ nonce: order.nonce as unknown as bigint,
356
+ sourceTokenAmount: null,
357
+ sourceFulfillerAddr: null,
358
+ sourceInitiateTxHash: null,
359
+ sourceStartTxHash: null,
360
+ sourceStatus: RozoPayOrderStatusSource.WAITING_PAYMENT,
361
+ destStatus: RozoPayOrderStatusDest.PENDING,
362
+ intentStatus: RozoPayIntentStatus.UNPAID,
363
+ destFastFinishTxHash: null,
364
+ destClaimTxHash: null,
365
+ redirectUri: null,
366
+ createdAt: Math.floor(Date.now() / 1000),
367
+ lastUpdatedAt: Math.floor(Date.now() / 1000),
368
+ orgId: order.orgId as string,
369
+ metadata: {
370
+ ...(order?.metadata ?? {}),
371
+ ...(order.userMetadata ?? {}),
372
+ ...(order.metadata ?? {}),
373
+ } as any,
374
+ externalId: order.externalId as string | null,
375
+ userMetadata: order.userMetadata as RozoPayUserMetadata | null,
376
+ expirationTs: BigInt(Math.floor(Date.now() / 1000 + 5 * 60).toString()),
377
+ org: {
378
+ orgId: order.orgId as string,
379
+ name: "Pay Rozo",
380
+ },
381
+ };
382
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export * from "./api";
1
2
  export * from "./assert";
3
+ export * from "./bridge";
2
4
  export * from "./chain";
3
5
  export * from "./daimoPay";
4
6
  export * from "./debug";