@x402r/refund 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @x402r/extensions/refund
2
+
3
+ Refund Helper Extension for x402 - enables merchants to route payments to DepositRelay contracts via escrow, providing refund and dispute resolution capabilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @x402r/extensions/refund
9
+ ```
10
+
11
+ ### Peer Dependencies
12
+
13
+ This package requires:
14
+ - `@x402/core` (^2.0.0)
15
+ - `@x402/evm` (^2.0.0)
16
+
17
+ Install them separately:
18
+ ```bash
19
+ npm install @x402/core @x402/evm
20
+ ```
21
+
22
+ ## Overview
23
+
24
+ The refund helper allows merchants to mark payment options as refundable, which routes payments through DepositRelay contracts into escrow accounts. This enables dispute resolution and refunds even if merchants are uncooperative.
25
+
26
+ ## For Merchants (Server-Side)
27
+
28
+ ### Step 1: Mark Payment Options as Refundable
29
+
30
+ Use `refundable()` to mark payment options that should support refunds:
31
+
32
+ ```typescript
33
+ import { refundable } from '@x402r/extensions/refund';
34
+
35
+ const option = refundable({
36
+ scheme: 'exact',
37
+ payTo: '0xmerchant123...', // Your merchant payout address
38
+ price: '$0.01',
39
+ network: 'eip155:84532',
40
+ });
41
+ ```
42
+
43
+ ### Step 2: Process Routes with DepositRelay
44
+
45
+ Use `withRefund()` to process route configurations and route refundable payments to DepositRelay:
46
+
47
+ ```typescript
48
+ import { refundable, withRefund } from '@x402r/extensions/refund';
49
+
50
+ const FACTORY_ADDRESS = '0xFactory123...'; // Any CREATE3-compatible factory
51
+
52
+ const routes = {
53
+ '/api': {
54
+ accepts: refundable({
55
+ scheme: 'exact',
56
+ payTo: '0xmerchant123...',
57
+ price: '$0.01',
58
+ network: 'eip155:84532',
59
+ }),
60
+ },
61
+ };
62
+
63
+ // Process routes to route refundable payments to DepositRelay
64
+ const processedRoutes = withRefund(routes, FACTORY_ADDRESS);
65
+
66
+ // Use processedRoutes with paymentMiddleware
67
+ app.use(paymentMiddleware(processedRoutes, server));
68
+ ```
69
+
70
+ ## For Facilitators
71
+
72
+ Facilitators use `settleWithRefundHelper()` in hooks to handle refund settlements:
73
+
74
+ ```typescript
75
+ import { settleWithRefundHelper } from '@x402r/extensions/refund';
76
+ import { x402Facilitator } from '@x402/core/facilitator';
77
+
78
+ facilitator.onBeforeSettle(async (context) => {
79
+ const result = await settleWithRefundHelper(
80
+ context.paymentPayload,
81
+ context.paymentRequirements,
82
+ signer,
83
+ );
84
+
85
+ if (result) {
86
+ // Refund was handled via DepositRelay
87
+ return { abort: true, reason: 'handled_by_refund_helper' };
88
+ }
89
+
90
+ return null; // Proceed with normal settlement
91
+ });
92
+ ```
93
+
94
+ ## How It Works
95
+
96
+ 1. **Merchant Setup**: Merchant deploys escrow via EscrowFactory and marks options with `refundable()`
97
+ 2. **Route Processing**: `withRefund()` sets `payTo` to DepositRelay address, stores original merchantPayout in `extra`
98
+ 3. **Client Payment**: Client makes payment to DepositRelay address (transparent to client)
99
+ 4. **Facilitator Settlement**: Facilitator detects refund payment, queries EscrowFactory for escrow, calls DepositRelay.executeDeposit()
100
+ 5. **Escrow Hold**: Funds are held in escrow, enabling dispute resolution and refunds
101
+
102
+ ## Key Features
103
+
104
+ - **No Core Changes**: Works entirely through helpers and hooks
105
+ - **Client Transparent**: Clients don't need to change anything
106
+ - **Flexible**: Mix refundable and non-refundable options in same route
107
+ - **Deep Cloning**: All helpers return new objects, don't mutate originals
108
+
109
+ ## API Reference
110
+
111
+ ### `refundable(option: PaymentOption): PaymentOption`
112
+
113
+ Marks a payment option as refundable. Returns a new PaymentOption with the refund marker set.
114
+
115
+ ### `withRefund(routes: RoutesConfig, factoryAddress: string, createxAddress?: string): RoutesConfig`
116
+
117
+ Processes route configuration to handle refundable payment options. Computes proxy addresses using CREATE3 and sets `payTo` to proxy for all refundable options.
118
+
119
+ ### `settleWithRefundHelper(paymentPayload, paymentRequirements, signer): Promise<SettleResponse | null>`
120
+
121
+ Helper for facilitator operators to handle refund settlements via X402DepositRelayProxy. Returns `SettleResponse` on success, `null` if not applicable.
122
+
123
+ ### `extractRefundInfo(paymentPayload, paymentRequirements): { factoryAddress: string; merchantPayouts: Record<string, string> } | null`
124
+
125
+ Extracts refund extension info from payment payload or requirements.
126
+
127
+ ### `computeRelayAddress(createxAddress: string, factoryAddress: string, merchantPayout: string): string`
128
+
129
+ Computes the CREATE3 address for a merchant's relay proxy.
130
+
131
+ ## Examples
132
+
133
+ See the [examples directory](../../examples/) for complete working examples:
134
+ - [Server Example](../../examples/server/) - Express.js server with refund-enabled endpoints
135
+ - [Facilitator Example](../../examples/facilitator/) - Facilitator with refund settlement handling
136
+
137
+ ## License
138
+
139
+ Apache-2.0
@@ -0,0 +1,253 @@
1
+ import { PaymentOption, RoutesConfig } from '@x402/core/http';
2
+ import { PaymentPayload, PaymentRequirements, SettleResponse } from '@x402/core/types';
3
+ import { FacilitatorEvmSigner } from '@x402/evm';
4
+
5
+ /**
6
+ * Type definitions for the Refund Helper Extension
7
+ */
8
+
9
+ /**
10
+ * Extension identifier constant for the refund extension
11
+ */
12
+ declare const REFUND_EXTENSION_KEY = "refund";
13
+ /**
14
+ * Constant for the refund marker key (internal marker)
15
+ * Used to identify refundable payment options
16
+ * The merchantPayout is read directly from the option's payTo field when processing
17
+ */
18
+ declare const REFUND_MARKER_KEY = "_x402_refund";
19
+ /**
20
+ * Refund extension info structure
21
+ *
22
+ * merchantPayouts: Map of proxy address -> merchantPayout
23
+ * This allows multiple refundable options with different merchantPayouts
24
+ */
25
+ interface RefundExtensionInfo {
26
+ factoryAddress: string;
27
+ merchantPayouts: Record<string, string>;
28
+ }
29
+ /**
30
+ * Refund extension structure (matches extension pattern with info and schema)
31
+ */
32
+ interface RefundExtension {
33
+ info: RefundExtensionInfo;
34
+ schema: {
35
+ $schema: "https://json-schema.org/draft/2020-12/schema";
36
+ type: "object";
37
+ properties: {
38
+ factoryAddress: {
39
+ type: "string";
40
+ pattern: "^0x[a-fA-F0-9]{40}$";
41
+ description: "The X402DepositRelayFactory contract address";
42
+ };
43
+ merchantPayouts: {
44
+ type: "object";
45
+ additionalProperties: {
46
+ type: "string";
47
+ pattern: "^0x[a-fA-F0-9]{40}$";
48
+ };
49
+ description: "Map of proxy address to merchant payout address";
50
+ };
51
+ };
52
+ required: ["factoryAddress", "merchantPayouts"];
53
+ additionalProperties: false;
54
+ };
55
+ }
56
+ /**
57
+ * Type guard to check if a payment option is refundable
58
+ * A refundable option has a marker stored in extra
59
+ *
60
+ * @param option - The payment option to check
61
+ * @returns True if the option is refundable
62
+ */
63
+ declare function isRefundableOption(option: PaymentOption): boolean;
64
+
65
+ /**
66
+ * Server-side helpers for the Refund Helper Extension
67
+ *
68
+ * These helpers allow merchants to mark payment options as refundable
69
+ * and process route configurations to route payments to X402DepositRelayProxy contracts.
70
+ */
71
+
72
+ /**
73
+ * Declares a refund extension with factory address and merchantPayouts map
74
+ *
75
+ * @param factoryAddress - The X402DepositRelayFactory contract address
76
+ * @param merchantPayouts - Map of proxy address to merchant payout address
77
+ * @returns Refund extension object with info and schema
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const extension = declareRefundExtension("0xFactory123...", {
82
+ * "0xProxy1...": "0xMerchant1...",
83
+ * "0xProxy2...": "0xMerchant2...",
84
+ * });
85
+ * ```
86
+ */
87
+ declare function declareRefundExtension(factoryAddress: string, merchantPayouts: Record<string, string>): Record<string, RefundExtension>;
88
+ /**
89
+ * Marks a payment option as refundable.
90
+ *
91
+ * This function marks the option as refundable so it can be processed by `withRefund()`.
92
+ * The merchantPayout is read directly from the option's `payTo` field when processing.
93
+ *
94
+ * @param option - The payment option to mark as refundable
95
+ * @returns A new PaymentOption marked as refundable (does not mutate original)
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * const refundableOption = refundable({
100
+ * scheme: "exact",
101
+ * payTo: "0xmerchant123...",
102
+ * price: "$0.01",
103
+ * network: "eip155:84532",
104
+ * });
105
+ * // refundableOption.extra._x402_refund = true
106
+ * ```
107
+ */
108
+ declare function refundable(option: PaymentOption): PaymentOption;
109
+ /**
110
+ * Processes route configuration to handle refundable payment options.
111
+ *
112
+ * This function finds all payment options marked with `refundable()` and:
113
+ * 1. Computes the proxy address using CREATE3 (no bytecode needed!)
114
+ * 2. Sets `payTo` to the proxy address
115
+ * 3. Adds the refund extension with factory address
116
+ *
117
+ * @param routes - Route configuration (single RouteConfig or Record<string, RouteConfig>)
118
+ * @param factoryAddress - The X402DepositRelayFactory contract address (required)
119
+ * @param createxAddress - The CreateX contract address (optional, will use standard address for network if not provided)
120
+ * @returns A new RoutesConfig with refundable options routed to proxy (deep cloned, does not mutate original)
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const routes = {
125
+ * "/api": {
126
+ * accepts: refundable({
127
+ * scheme: "exact",
128
+ * payTo: "0xmerchant123...",
129
+ * price: "$0.01",
130
+ * network: "eip155:84532",
131
+ * }),
132
+ * },
133
+ * };
134
+ *
135
+ * // Version is optional - defaults to config file value
136
+ * // CreateX address is optional - uses standard address for the network
137
+ * const processedRoutes = withRefund(routes, "0xFactory123...");
138
+ * // processedRoutes["/api"].accepts.payTo = computed proxy address
139
+ * // processedRoutes["/api"].extensions.refund = { info: { factoryAddress: "0xFactory123..." }, schema: {...} }
140
+ * ```
141
+ */
142
+ declare function withRefund(routes: RoutesConfig, factoryAddress: string, createxAddress?: string): RoutesConfig;
143
+
144
+ /**
145
+ * Helper to compute CREATE3 address for RelayProxy
146
+ *
147
+ * Uses the CREATE3 formula via CreateX (matching Solidity implementation):
148
+ *
149
+ * Where:
150
+ * - salt = keccak256(abi.encodePacked(factoryAddress, merchantPayout))
151
+ * - guardedSalt = keccak256(abi.encode(salt)) // CreateX guards the salt
152
+ * - createxDeployer = the CreateX contract address
153
+ *
154
+ * CREATE3 is much simpler than CREATE2 - no bytecode needed!
155
+ * The address depends only on the deployer (CreateX) and salt.
156
+ *
157
+ * IMPORTANT: The CreateX address must match the one used by the factory contract.
158
+ * The factory stores its CreateX address and can be queried via factory.getCreateX().
159
+ * This function computes addresses locally without any on-chain calls.
160
+ */
161
+ /**
162
+ * Computes the CREATE3 address for a merchant's relay proxy
163
+ *
164
+ * This matches the Solidity implementation in DepositRelayFactory.getRelayAddress():
165
+ * 1. salt = keccak256(abi.encodePacked(merchantPayout))
166
+ * 2. guardedSalt = keccak256(abi.encode(salt)) // CreateX guards the salt
167
+ * 3. return CREATEX.computeCreate3Address(guardedSalt)
168
+ *
169
+ * Uses the @whoislewys/predict-deterministic-address library which correctly
170
+ * implements the CREATE3 formula used by CreateX (based on Solady's CREATE3).
171
+ * This ensures the computed address matches the factory's on-chain computation
172
+ * without requiring any on-chain calls.
173
+ *
174
+ * @param createxAddress - The CreateX contract address
175
+ * @param factoryAddress - The DepositRelayFactory contract address
176
+ * @param merchantPayout - The merchant's payout address
177
+ * @returns The deterministic proxy address
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * // No bytecode needed! Computes locally without on-chain calls.
182
+ * const relayAddress = computeRelayAddress(
183
+ * "0xCreateX123...",
184
+ * "0xMerchant123...",
185
+ * 0n // version
186
+ * );
187
+ * ```
188
+ */
189
+ declare function computeRelayAddress(createxAddress: string, factoryAddress: string, merchantPayout: string): string;
190
+
191
+ /**
192
+ * Facilitator-side helpers for the Refund Helper Extension
193
+ *
194
+ * These helpers allow facilitator operators to validate refund info
195
+ * and handle refund settlements via X402DepositRelayProxy contracts.
196
+ */
197
+
198
+ /**
199
+ * Extracts refund extension info from payment payload or requirements
200
+ *
201
+ * @param paymentPayload - The payment payload (may contain extensions)
202
+ * @param _ - The payment requirements (currently unused, kept for API compatibility)
203
+ * @returns Refund extension info if valid, null otherwise
204
+ */
205
+ declare function extractRefundInfo(paymentPayload: PaymentPayload, _: PaymentRequirements): {
206
+ factoryAddress: string;
207
+ merchantPayouts: Record<string, string>;
208
+ } | null;
209
+ /**
210
+ * Helper for facilitator operators to handle refund settlements via X402DepositRelayProxy.
211
+ *
212
+ * This function:
213
+ * 1. Extracts refund extension info (factory address)
214
+ * 2. Validates factory exists
215
+ * 3. Reads merchantPayout and escrow directly from proxy storage
216
+ * 4. Checks if merchant is registered
217
+ * 5. Deploys relay on-demand if needed (via factory)
218
+ * 6. Calls proxy.executeDeposit() to deposit funds into escrow
219
+ *
220
+ * Returns null if refund is not applicable (delegates to normal flow).
221
+ * Throws error on execution failure or if merchant not registered (facilitator should handle in hook).
222
+ *
223
+ * @param paymentPayload - The payment payload containing authorization and signature
224
+ * @param paymentRequirements - The payment requirements containing refund extension
225
+ * @param signer - The EVM signer for contract interactions
226
+ * @returns SettleResponse on success, null if not applicable
227
+ * @throws Error on execution failure or if merchant not registered
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * facilitator.onBeforeSettle(async (context) => {
232
+ * try {
233
+ * const result = await settleWithRefundHelper(
234
+ * context.paymentPayload,
235
+ * context.paymentRequirements,
236
+ * signer,
237
+ * );
238
+ *
239
+ * if (result) {
240
+ * return { abort: true, reason: 'handled_by_refund_helper' };
241
+ * }
242
+ * } catch (error) {
243
+ * // Log error but don't abort - let normal settlement proceed
244
+ * console.error('Refund helper settlement failed:', error);
245
+ * }
246
+ *
247
+ * return null; // Proceed with normal settlement
248
+ * });
249
+ * ```
250
+ */
251
+ declare function settleWithRefundHelper(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements, signer: FacilitatorEvmSigner): Promise<SettleResponse | null>;
252
+
253
+ export { REFUND_EXTENSION_KEY, REFUND_MARKER_KEY, type RefundExtension, type RefundExtensionInfo, computeRelayAddress, declareRefundExtension, extractRefundInfo, isRefundableOption, refundable, settleWithRefundHelper, withRefund };