@rozoai/intent-common 0.0.28-beta.1 → 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/daimoPay.ts CHANGED
@@ -61,6 +61,7 @@ export enum RozoPayIntentStatus {
61
61
  STARTED = "payment_started",
62
62
  COMPLETED = "payment_completed",
63
63
  BOUNCED = "payment_bounced",
64
+ PAYOUT_COMPLETED = "payout_completed",
64
65
  }
65
66
 
66
67
  export interface RozoPayOrderItem {
@@ -223,6 +224,7 @@ export type RozoPayHydratedOrder = {
223
224
  /** Nullable because old intents don't have expiration time. */
224
225
  expirationTs: bigint | null;
225
226
  memo?: string | null;
227
+ payoutTransactionHash?: string | null;
226
228
  };
227
229
 
228
230
  export type RozoPayOrderWithOrg = RozoPayOrder & {
@@ -518,6 +520,7 @@ export enum RozoPayEventType {
518
520
  PaymentCompleted = "payment_completed",
519
521
  PaymentBounced = "payment_bounced",
520
522
  PaymentRefunded = "payment_refunded",
523
+ PaymentPayoutCompleted = "payment_payout_completed",
521
524
  }
522
525
 
523
526
  export type PaymentStartedEvent = {
@@ -539,6 +542,24 @@ export type PaymentCompletedEvent = {
539
542
  rozoPaymentId?: string;
540
543
  };
541
544
 
545
+ export type PaymentPayoutCompletedEvent = {
546
+ type: RozoPayEventType.PaymentPayoutCompleted;
547
+ isTestEvent?: boolean;
548
+ paymentId: RozoPayOrderID;
549
+ paymentTx: {
550
+ hash: string;
551
+ chainId: number;
552
+ url: string;
553
+ };
554
+ payoutTx: {
555
+ hash: string;
556
+ chainId: number;
557
+ url: string;
558
+ };
559
+ payment: RozoPayOrderView;
560
+ rozoPaymentId?: string;
561
+ };
562
+
542
563
  export type PaymentBouncedEvent = {
543
564
  type: RozoPayEventType.PaymentBounced;
544
565
  isTestEvent?: boolean;
@@ -565,7 +586,8 @@ export type RozoPayEvent =
565
586
  | PaymentStartedEvent
566
587
  | PaymentCompletedEvent
567
588
  | PaymentBouncedEvent
568
- | PaymentRefundedEvent;
589
+ | PaymentRefundedEvent
590
+ | PaymentPayoutCompletedEvent;
569
591
 
570
592
  export interface WebhookEndpoint {
571
593
  id: UUID;
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";