@phala/pay 0.1.2 → 0.2.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 (88) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +129 -28
  3. package/dist/checkout.d.ts +37 -7
  4. package/dist/checkout.d.ts.map +1 -1
  5. package/dist/checkout.js +55 -11
  6. package/dist/checkout.js.map +1 -1
  7. package/dist/client.d.ts +8 -7
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +14 -8
  10. package/dist/client.js.map +1 -1
  11. package/dist/deposit-address.d.ts +47 -0
  12. package/dist/deposit-address.d.ts.map +1 -0
  13. package/dist/deposit-address.js +88 -0
  14. package/dist/deposit-address.js.map +1 -0
  15. package/dist/index.d.ts +3 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/payment.d.ts +43 -1
  20. package/dist/payment.d.ts.map +1 -1
  21. package/dist/payment.js +32 -10
  22. package/dist/payment.js.map +1 -1
  23. package/dist/quote.d.ts +7 -3
  24. package/dist/quote.d.ts.map +1 -1
  25. package/dist/quote.js +3 -1
  26. package/dist/quote.js.map +1 -1
  27. package/dist/react/Checkout.d.ts +11 -1
  28. package/dist/react/Checkout.d.ts.map +1 -1
  29. package/dist/react/Checkout.js +28 -37
  30. package/dist/react/Checkout.js.map +1 -1
  31. package/dist/react/DepositAddress.d.ts +31 -0
  32. package/dist/react/DepositAddress.d.ts.map +1 -0
  33. package/dist/react/DepositAddress.js +108 -0
  34. package/dist/react/DepositAddress.js.map +1 -0
  35. package/dist/react/Field.d.ts +7 -0
  36. package/dist/react/Field.d.ts.map +1 -0
  37. package/dist/react/Field.js +21 -0
  38. package/dist/react/Field.js.map +1 -0
  39. package/dist/react/appearance.d.ts +1 -1
  40. package/dist/react/appearance.d.ts.map +1 -1
  41. package/dist/react/appearance.js +3 -0
  42. package/dist/react/appearance.js.map +1 -1
  43. package/dist/react/index.d.ts +1 -0
  44. package/dist/react/index.d.ts.map +1 -1
  45. package/dist/react/index.js +1 -0
  46. package/dist/react/index.js.map +1 -1
  47. package/dist/react/useCheckout.d.ts +3 -1
  48. package/dist/react/useCheckout.d.ts.map +1 -1
  49. package/dist/react/useCheckout.js +4 -3
  50. package/dist/react/useCheckout.js.map +1 -1
  51. package/dist/server/addresses.d.ts +78 -0
  52. package/dist/server/addresses.d.ts.map +1 -0
  53. package/dist/server/addresses.js +118 -0
  54. package/dist/server/addresses.js.map +1 -0
  55. package/dist/server/index.d.ts +12 -0
  56. package/dist/server/index.d.ts.map +1 -0
  57. package/dist/server/index.js +12 -0
  58. package/dist/server/index.js.map +1 -0
  59. package/dist/server/sweeps.d.ts +78 -0
  60. package/dist/server/sweeps.d.ts.map +1 -0
  61. package/dist/server/sweeps.js +117 -0
  62. package/dist/server/sweeps.js.map +1 -0
  63. package/dist/server/webhook.d.ts +60 -0
  64. package/dist/server/webhook.d.ts.map +1 -0
  65. package/dist/server/webhook.js +148 -0
  66. package/dist/server/webhook.js.map +1 -0
  67. package/dist/wallet.d.ts +10 -8
  68. package/dist/wallet.d.ts.map +1 -1
  69. package/dist/wallet.js +61 -63
  70. package/dist/wallet.js.map +1 -1
  71. package/package.json +6 -1
  72. package/src/checkout.ts +89 -15
  73. package/src/client.ts +14 -8
  74. package/src/deposit-address.ts +138 -0
  75. package/src/index.ts +19 -1
  76. package/src/payment.ts +72 -10
  77. package/src/quote.ts +10 -4
  78. package/src/react/Checkout.tsx +72 -70
  79. package/src/react/DepositAddress.tsx +213 -0
  80. package/src/react/Field.tsx +46 -0
  81. package/src/react/appearance.ts +3 -0
  82. package/src/react/index.ts +1 -0
  83. package/src/react/useCheckout.ts +6 -2
  84. package/src/server/addresses.ts +223 -0
  85. package/src/server/index.ts +38 -0
  86. package/src/server/sweeps.ts +197 -0
  87. package/src/server/webhook.ts +202 -0
  88. package/src/wallet.ts +74 -68
package/CHANGELOG.md CHANGED
@@ -6,10 +6,82 @@ All notable changes to `@phala/pay` are recorded here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-09-29
10
+
11
+ ### Changed (breaking)
12
+
13
+ - `quoteAddress(forwarder, quote, treasury, account)` takes the treasury explicitly, like
14
+ `depositAddress`; it no longer reads the quote's own `treasury`, which a compromised service
15
+ could set to an attacker's with that treasury's valid address.
16
+
17
+ - `quoteSalt(account, clientReferenceId, quoteId)` replaces `lockSalt`: a quote's address salt is
18
+ tagged `"quote"` (design D3; it was `"lock"`), so `quoteAddress` recomputes the service's
19
+ addresses again.
20
+
21
+ - `<Checkout expectedAddress>` is required, as are `expectedAddress` in `createCheckout`,
22
+ `useCheckout`, `PhalaPay.checkout(clientSecret, { expectedAddress })`, and the second argument of
23
+ `retrieveQuote`/`PhalaPay.retrieveQuote`: the address your backend recomputed. A quote naming
24
+ another address fails closed with `CheckoutError` `address_mismatch`; the component shows
25
+ nothing to pay.
26
+ - `ClientQuote` has `livemode` (the checkout shows "Test mode") and `payment_status: "reversed"`;
27
+ `CheckoutStatus` gains `reversed`, with "reversed" copy.
28
+
29
+ ### Added
30
+
31
+ - `verifyQuoteAddress(pins, quote)` and `verifyDepositAddress(pins, address)` in
32
+ `@phala/pay/server` recompute an address from `AddressPins` you configure (`account`, `factory`,
33
+ `implementation`, `treasuries` per chain), never from the response's treasury, and throw
34
+ `AddressMismatchError`; in live mode a chain without a pinned treasury fails closed, and test mode
35
+ falls back to the response's treasury with a console warning.
36
+
37
+ - `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
38
+ `retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
39
+ before reading again.
40
+ - `WebhookEvent.request` (the request that caused the event, or `null` for the service's
41
+ workers) and the documented `data.previous_attributes` of `*.updated` events.
42
+
43
+ - `<DepositAddress clientSecret apiBase pollInterval?>` follows the address's payments and shows
44
+ each within about a block of arriving ("1.5 PHA received on Sepolia, 1 confirmation"), then
45
+ credited, rejected, or reversed; `retrieveDepositAddress`, `parseClientDepositAddress`, and
46
+ `depositAddressIdFromClientSecret` read the same public view.
47
+ - `@phala/pay/server`, which takes no secret key: `constructEvent(payload, headers, publicKeys,
48
+ { expectedAccount, expectedLivemode })` verifies Standard Webhooks `v1a` deliveries with
49
+ WebCrypto (Node 20+, Deno, Bun, edge runtimes), failing closed for another account or mode;
50
+ `forwarderAddress`, `quoteSalt`, `depositAddressSalt`, `quoteAddress`, and `depositAddress`
51
+ recompute addresses; `flushTransaction`, `flushTransactions`, `safeBatch`, and `batchChecksum`
52
+ build sweeps offline, with the Transaction Builder's `BatchFile` type.
53
+
54
+ - `<DepositAddress depositAddress chainId? asset?>` (`@phala/pay/react`): a customer's persistent
55
+ deposit address, one address for all supported tokens and networks. The payer picks a network
56
+ and a token; it shows a QR code of that token's EIP-681 request, the token contract and address
57
+ to copy (each network's own address where they differ), and "send only supported tokens; any
58
+ amount, credited at the market rate when it arrives" copy. `depositAddressTransfer(network,
59
+ asset)` reads and checks one token's amount-less `payment_uri`, and `parseTransferUri(uri)` is
60
+ the EIP-681 ERC-20 transfer parser `quoteTransfer` uses.
61
+
62
+ - `<Checkout walletClient>` and `payWithWallet(walletClient, quote)`: pay with the page's own viem
63
+ `WalletClient`, such as wagmi's `useWalletClient()` (RainbowKit, ConnectKit, AppKit). The wallet
64
+ tab then uses its account and switches its chain, without listing the browser's wallets.
65
+
66
+ ### Changed
67
+
68
+ - `<Checkout>` copy for fast credit: a seen payment reads "Payment received, N confirmations.
69
+ Crediting in about 30 seconds"; `confirming` and `credited` are documented for crediting at the
70
+ route's confirmation (two blocks on Ethereum) instead of finality.
71
+ - `watchWallets` discovers EIP-6963 wallets through [mipd](https://github.com/wevm/mipd), wagmi's
72
+ discovery store, now a dependency; the `window.ethereum` fallback stays. Wallet errors are
73
+ classified with viem's `UserRejectedRequestError` and `SwitchChainError`.
74
+
9
75
  ## [0.1.2] - 2026-09-27
10
76
 
11
77
  ### Added
12
78
 
79
+ - `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
80
+ `retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
81
+ before reading again.
82
+ - `WebhookEvent.request` (the request that caused the event, or `null` for the service's
83
+ workers) and the documented `data.previous_attributes` of `*.updated` events.
84
+
13
85
  - `<Checkout onChange>`: called once per status change with `{ status, quote, error }`, like
14
86
  Stripe Elements' `onChange`, so the page can react to `seen` or `confirming` without its own
15
87
  polling.
@@ -23,6 +95,12 @@ All notable changes to `@phala/pay` are recorded here. The format follows
23
95
 
24
96
  ### Added
25
97
 
98
+ - `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
99
+ `retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
100
+ before reading again.
101
+ - `WebhookEvent.request` (the request that caused the event, or `null` for the service's
102
+ workers) and the documented `data.previous_attributes` of `*.updated` events.
103
+
26
104
  - `appearance.variables.accessibleColorOnColorPrimary` (`--pp-accessible-color-on-color-primary`,
27
105
  default `#ffffff`): the color of text on a `colorPrimary` background, such as the pay button's
28
106
  label, so a light brand primary stays readable. Same name and meaning as Stripe's current
@@ -38,6 +116,12 @@ All notable changes to `@phala/pay` are recorded here. The format follows
38
116
 
39
117
  ### Added
40
118
 
119
+ - `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
120
+ `retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
121
+ before reading again.
122
+ - `WebhookEvent.request` (the request that caused the event, or `null` for the service's
123
+ workers) and the documented `data.previous_attributes` of `*.updated` events.
124
+
41
125
  - `PhalaPay({ apiBase })`: `retrieveQuote(clientSecret)` reads a quote's public view
42
126
  (`GET /v1/quotes/{id}?client_secret=…`) and `checkout(clientSecret)` follows it, exposing the
43
127
  payer-facing status (also `createCheckout` and `retrieveQuote`).
@@ -49,7 +133,8 @@ All notable changes to `@phala/pay` are recorded here. The format follows
49
133
  without trailing zeros (`1,273.9185`); `tokenAmount(quote)` is the plain decimal a wallet
50
134
  accepts, which `<Checkout>`'s "Exact amount" copies.
51
135
 
52
- [unreleased]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.2...HEAD
136
+ [unreleased]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.2.0...HEAD
137
+ [0.2.0]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.2...sdk-js-v0.2.0
53
138
  [0.1.2]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.1...sdk-js-v0.1.2
54
139
  [0.1.1]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.0...sdk-js-v0.1.1
55
140
  [0.1.0]: https://github.com/Phala-Network/phala-pay/releases/tag/sdk-js-v0.1.0
package/README.md CHANGED
@@ -11,38 +11,34 @@ Python SDK (`phala-pay`) and fulfils from the signed `deposit.credited` webhook.
11
11
  npm install @phala/pay viem
12
12
  ```
13
13
 
14
- Peer dependencies: `viem` 2, and `react` 18 or 19 for `@phala/pay/react`. Nothing else:
15
- wallets are found with EIP-6963 (with a `window.ethereum` fallback), not wagmi or WalletConnect.
16
-
17
- Until the first npm release, install from the repository with pnpm. The package builds itself
18
- on install, which pnpm allows only for listed git dependencies: the first `pnpm add` stops and
19
- prints an `allowBuilds` entry naming the exact commit; add it to `pnpm-workspace.yaml` and run the
20
- same command again.
21
-
22
- ```sh
23
- pnpm add "github:Phala-Network/phala-pay#main&path:/sdk/js"
24
- ```
25
-
26
- ```yaml
27
- # pnpm-workspace.yaml: the entry pnpm printed
28
- allowBuilds:
29
- "@phala/pay@https://codeload.github.com/Phala-Network/phala-pay/tar.gz/<commit>#path:/sdk/js": true
30
- ```
14
+ Peer dependencies: `viem` 2, and `react` 18 or 19 for `@phala/pay/react`. Browser wallets are
15
+ found with EIP-6963 through [mipd](https://github.com/wevm/mipd), wagmi's discovery store (with a
16
+ `window.ethereum` fallback for wallets that do not announce themselves). If your page already
17
+ connects a wallet with wagmi, RainbowKit, ConnectKit, or AppKit, pass it in instead; see
18
+ [Your own wallet connection](#your-own-wallet-connection).
31
19
 
32
20
  ## Quickstart
33
21
 
34
- 1. Your backend creates a quote (`POST /v1/quotes`, signed with the product key) and returns only its
35
- `client_secret` to the signed-in user's browser.
36
- 2. Render the checkout with it:
22
+ 1. Your backend creates a quote (`POST /v1/quotes` with its secret key, for example
23
+ `phala-pay`'s `pay.quotes.create`), recomputes its address from the pinned forwarder (the
24
+ Python SDK does it on every call), and returns only its `client_secret` and that address to
25
+ the signed-in user's browser.
26
+ 2. Render the checkout with them:
37
27
 
38
28
  ```tsx
39
29
  "use client";
40
30
  import { Checkout } from "@phala/pay/react";
41
31
 
42
- export function TopUp(props: { clientSecret: string; onPaid: () => void; onRetry: () => void }) {
32
+ export function TopUp(props: {
33
+ clientSecret: string;
34
+ expectedAddress: string;
35
+ onPaid: () => void;
36
+ onRetry: () => void;
37
+ }) {
43
38
  return (
44
39
  <Checkout
45
40
  clientSecret={props.clientSecret}
41
+ expectedAddress={props.expectedAddress}
46
42
  apiBase="https://pay.example.com"
47
43
  onSuccess={props.onPaid}
48
44
  onExpire={props.onRetry}
@@ -59,6 +55,10 @@ The component reads `GET /v1/quotes/{id}?client_secret=…` every three seconds.
59
55
  public (`Access-Control-Allow-Origin: *`) and shows only what the payer needs: the amount, the
60
56
  token, the chain, the deposit address, the EIP-681 payment request, the expiry, and the payment
61
57
  status. Keep the client secret out of logs and URLs you share; anyone holding it can see that view.
58
+ `expectedAddress` is required and fails closed: when the quote the service returns names another
59
+ address, the checkout shows "This payment address could not be verified" and nothing to pay (a
60
+ compromised service cannot make the page show its own address). A test-mode quote says "Test
61
+ mode".
62
62
 
63
63
  ### Statuses
64
64
 
@@ -67,9 +67,10 @@ status. Keep the client secret out of logs and URLs you share; anyone holding it
67
67
  | `loading` | Loading payment details |
68
68
  | `waiting` | Payment options, the exact amount, and the time left |
69
69
  | `seen` | Payment in a block, with its confirmations (a reorg can still remove it) |
70
- | `confirming` | Payment final, being valued and screened |
71
- | `credited` | Credited; `onSuccess` is called once |
72
- | `rejected` | Final but will not be credited; the payer contacts support |
70
+ | `confirming` | Payment at the route's confirmation (two blocks on Ethereum), being valued and screened |
71
+ | `credited` | Credited, typically about 30 seconds after paying; `onSuccess` is called once |
72
+ | `rejected` | Will not be credited; the payer contacts support |
73
+ | `reversed` | Credited, then its transaction left the chain before finality: the payment did not happen |
73
74
  | `expired`, `canceled` | The address is hidden; `onExpire` is called once |
74
75
  | `error` | The client secret is not valid |
75
76
 
@@ -77,14 +78,81 @@ status. Keep the client secret out of logs and URLs you share; anyone holding it
77
78
  Elements' `onChange`, for example to hide your own "new payment" control while a payment is
78
79
  `seen` or `confirming`.
79
80
 
81
+ To resume after a reload, keep the client secret in the browser (for example `localStorage`, per
82
+ signed-in account) until the status is `credited`, `expired`, `canceled`, or `error`. Tell a
83
+ payer who is waiting that the payment is credited in about 30 seconds and the credit arrives on
84
+ its own.
85
+
86
+ A credit is made before Ethereum finality (about 15 minutes). In the rare case that a reorg drops
87
+ the payment's transaction, your backend receives `deposit.reversed` and claws the credit back as
88
+ for `deposit.refunded`; the checkout itself only reflects the quote.
89
+
80
90
  The payment options disappear once a payment is seen, and at `expires_at`. A payment of a different
81
91
  amount, or after expiry, is still credited, at the market price instead of the quote's.
82
92
 
93
+ ### Your own wallet connection
94
+
95
+ By default the wallet tab lists the browser's wallets. If your page already connects one, pass its
96
+ viem `WalletClient` as `walletClient`: the wallet tab then pays with that client and its account
97
+ (switching its chain to the quote's) and lists nothing else. With wagmi, including RainbowKit,
98
+ ConnectKit, and AppKit, that is `useWalletClient()`:
99
+
100
+ ```tsx
101
+ "use client";
102
+ import { Checkout } from "@phala/pay/react";
103
+ import { useWalletClient } from "wagmi";
104
+
105
+ export function TopUp({ clientSecret }: { clientSecret: string }) {
106
+ const { data: walletClient } = useWalletClient();
107
+ return (
108
+ <Checkout
109
+ clientSecret={clientSecret}
110
+ expectedAddress={expectedAddress}
111
+ apiBase="https://pay.example.com"
112
+ walletClient={walletClient}
113
+ />
114
+ );
115
+ }
116
+ ```
117
+
118
+ While no wallet is connected (`walletClient` is `undefined`), the tab lists the browser's wallets
119
+ as usual. The QR code and manual tabs do not change.
120
+
121
+ ### A customer's deposit address
122
+
123
+ For top-ups of any amount, your backend creates the customer's persistent deposit address with
124
+ `POST /v1/deposit_addresses` (secret key) and passes its `address` and `networks` to the page:
125
+ one address for all supported tokens and networks. `<DepositAddress>` lets the payer pick a
126
+ network and a token and shows a QR code of that token's transfer request, the token contract, and
127
+ the address to copy, with "send only supported tokens" and "credited at the market rate when it
128
+ arrives" copy; where a network's address differs (another treasury), it shows that network's own.
129
+ Credit the customer from the `deposit.credited` webhook.
130
+
131
+ ```tsx
132
+ import { DepositAddress } from "@phala/pay/react";
133
+
134
+ <DepositAddress
135
+ depositAddress={{ address, networks }}
136
+ clientSecret={clientSecret} // the same response's client_secret: shows payments as they arrive
137
+ apiBase="https://pay.example.com"
138
+ chainId={84532}
139
+ asset="usdc"
140
+ />;
141
+ ```
142
+
143
+ With `clientSecret` and `apiBase` it reads the address's public view every three seconds and lists
144
+ its payments of the last 24 hours: "1.5 PHA received on Sepolia, 1 confirmation" within about a
145
+ block of the transfer, then "credited" (or rejected or reversed). Display only.
146
+
147
+ `depositAddressTransfer(network, asset)` reads and checks one token's amount-less EIP-681
148
+ `payment_uri` without React.
149
+
83
150
  ## Appearance
84
151
 
85
152
  ```tsx
86
153
  <Checkout
87
154
  clientSecret={clientSecret}
155
+ expectedAddress={expectedAddress}
88
156
  apiBase={apiBase}
89
157
  appearance={{
90
158
  theme: "dark",
@@ -121,8 +189,8 @@ readable.
121
189
  import { PhalaPay, payWithWallet, watchWallets, type Wallet } from "@phala/pay";
122
190
 
123
191
  const pay = new PhalaPay({ apiBase: "https://pay.example.com" });
124
- const quote = await pay.retrieveQuote(clientSecret); // one read of the public view
125
- const checkout = pay.checkout(clientSecret); // or follow it until it settles
192
+ const quote = await pay.retrieveQuote(clientSecret, expectedAddress); // one read, checked
193
+ const checkout = pay.checkout(clientSecret, { expectedAddress }); // or follow it until it settles
126
194
  const unsubscribe = checkout.subscribe(({ status, quote, error }) => render(status, quote, error));
127
195
 
128
196
  const stop = watchWallets((wallets) => showWalletButtons(wallets));
@@ -142,10 +210,42 @@ unsubscribe();
142
210
  checkout.destroy();
143
211
  ```
144
212
 
145
- `payWithWallet` connects, switches the wallet to the quote's chain (adding Ethereum, Sepolia,
213
+ `payWithWallet` takes a discovered wallet's EIP-1193 provider or a viem `WalletClient` (such as
214
+ wagmi's `useWalletClient()` or `getWalletClient(config)`; its account is used without asking to
215
+ connect again). It connects, switches the wallet to the quote's chain (adding Ethereum, Sepolia,
146
216
  Base, or Base Sepolia when the wallet lacks it), and sends the ERC-20 `transfer` stated by the
147
217
  quote's `payment_uri`, after checking that it pays exactly `amount_atomic` to `address`.
148
218
 
219
+ ## Server helpers
220
+
221
+ `@phala/pay/server` is for your backend and takes no API key (call the API itself with a
222
+ restricted key, `ppay_rk_…`, from the backend only, never the browser):
223
+
224
+ ```ts
225
+ import { constructEvent, flushTransactions, safeBatch, verifyQuoteAddress } from "@phala/pay/server";
226
+
227
+ // Your pins, configured on your server and never read from the service. The address is derived
228
+ // from your treasury, not the quote's: a mismatch throws AddressMismatchError, and in live mode a
229
+ // chain without a pinned treasury fails closed.
230
+ const pins = { account: "acct_…", factory: FACTORY, implementation: IMPLEMENTATION, treasuries: { 1: TREASURY } };
231
+ const expectedAddress = verifyQuoteAddress(pins, quote); // pass it to <Checkout expectedAddress>
232
+
233
+ // Standard Webhooks v1a (ed25519, WebCrypto: Node 20+, Deno, Bun, edge runtimes). Fails closed
234
+ // unless the signature verifies with a pinned key and the event is your account's in this mode.
235
+ const event = await constructEvent(rawBody, request.headers, WEBHOOK_PUBLIC_KEYS, {
236
+ expectedAccount: "acct_…",
237
+ expectedLivemode: false,
238
+ });
239
+
240
+ // Sweeping, offline: forwarders from GET /v1/forwarders?sweepable=<token>.
241
+ const calls = flushTransactions(forwarders, PHA);
242
+ const batchFile = safeBatch(1, TREASURY_SAFE, calls); // Safe Transaction Builder JSON
243
+ ```
244
+
245
+ `verifyDepositAddress(pins, address)` checks every network of a deposit address the same way.
246
+ `quoteAddress`, `depositAddress`, `forwarderAddress`, `quoteSalt`, and `depositAddressSalt`
247
+ recompute an address offline from a treasury you pass, as the Python SDK does.
248
+
149
249
  ## Development
150
250
 
151
251
  ```sh
@@ -156,7 +256,8 @@ pnpm run e2e:docker # the same, with Chromium from Playwright's image (as CI ru
156
256
  ```
157
257
 
158
258
  The end-to-end tests start Anvil as Sepolia, deploy a test token, and pay a quote from a mocked
159
- EIP-6963 wallet backed by the node, then decode the QR code and exercise the manual details.
259
+ EIP-6963 wallet backed by the node (and again through a viem `WalletClient` passed as
260
+ `walletClient`), then decode the QR code and exercise the manual details.
160
261
 
161
262
  ## Releases
162
263
 
@@ -3,18 +3,39 @@ import { type ClientQuote } from "./quote.js";
3
3
  * What the payer sees:
4
4
  * - `loading` until the quote is first read;
5
5
  * - `waiting` for a payment, until `expires_at`;
6
- * - `seen` once a transfer is in a block, `confirming` once it is final and being valued;
7
- * - `credited`, or `rejected` for a final payment that will not be credited;
6
+ * - `seen` once a transfer is in a block, `confirming` once it reaches the route's confirmation
7
+ * (two blocks on Ethereum) and is being valued;
8
+ * - `credited` (about 30 seconds after paying), or `rejected` for a payment that will not be
9
+ * credited;
10
+ * - `reversed` when a credited payment's transaction left the chain before finality;
8
11
  * - `expired` or `canceled` without a payment;
9
- * - `error` when the client secret is not valid.
12
+ * - `error` when the client secret is not valid, or the quote's address is not the one your
13
+ * backend expects (`address_mismatch`): nothing is shown to pay.
10
14
  */
11
- export type CheckoutStatus = "loading" | "waiting" | "seen" | "confirming" | "credited" | "rejected" | "expired" | "canceled" | "error";
12
- export type CheckoutErrorCode = "invalid_client_secret" | "rate_limited" | "network_error" | "invalid_response" | "api_error";
15
+ export type CheckoutStatus = "loading" | "waiting" | "seen" | "confirming" | "credited" | "rejected" | "reversed" | "expired" | "canceled" | "error";
16
+ export type CheckoutErrorCode = "invalid_client_secret" | "address_mismatch" | "rate_limited" | "network_error" | "invalid_response" | "api_error";
17
+ export interface CheckoutErrorOptions extends ErrorOptions {
18
+ /** The failed response's `Request-Id`, `req_…`, to quote to support. */
19
+ requestId?: string;
20
+ /** For `rate_limited`: the seconds the service asked to wait (`Retry-After`). */
21
+ retryAfter?: number;
22
+ }
13
23
  export declare class CheckoutError extends Error {
14
24
  readonly code: CheckoutErrorCode;
15
25
  readonly name = "CheckoutError";
16
- constructor(code: CheckoutErrorCode, message: string, options?: ErrorOptions);
26
+ /** The failed response's `Request-Id`, when the service answered. */
27
+ readonly requestId: string | undefined;
28
+ /** For `rate_limited`: the seconds to wait before the next read. */
29
+ readonly retryAfter: number | undefined;
30
+ constructor(code: CheckoutErrorCode, message: string, options?: CheckoutErrorOptions);
17
31
  }
32
+ /**
33
+ * The error of a failed read of a public view, with the response's `Request-Id` and, on `429`,
34
+ * its `Retry-After`.
35
+ */
36
+ export declare function responseError(response: Response, notFound: string): CheckoutError;
37
+ /** The delay before the next read after `failures` failed ones, at least what `error` asks. */
38
+ export declare function pollDelay(interval: number, failures: number, error: CheckoutError | null): number;
18
39
  export interface CheckoutState {
19
40
  status: CheckoutStatus;
20
41
  quote: ClientQuote | null;
@@ -24,6 +45,12 @@ export interface CheckoutState {
24
45
  export interface CheckoutOptions {
25
46
  /** The quote's `client_secret`, from your backend's `POST /v1/quotes`. */
26
47
  clientSecret: string;
48
+ /**
49
+ * The quote's address as your backend's SDK recomputed it from the pinned forwarder. The
50
+ * checkout fails closed, showing nothing to pay, when the quote read from the service names
51
+ * another address.
52
+ */
53
+ expectedAddress: string;
27
54
  /** The service origin, for example `https://topup.example.com`. */
28
55
  apiBase: string;
29
56
  /** Milliseconds between status reads; default 3000. */
@@ -43,12 +70,15 @@ export interface CheckoutSession {
43
70
  }
44
71
  export interface RetrieveQuoteOptions {
45
72
  clientSecret: string;
73
+ /** The address your backend recomputed; a quote naming another one is refused. */
74
+ expectedAddress: string;
46
75
  apiBase: string;
47
76
  fetch?: typeof globalThis.fetch;
48
77
  }
49
78
  /**
50
79
  * Reads a quote's public view once, as Stripe.js's `retrievePaymentIntent(clientSecret)` does.
51
- * Rejects with a `CheckoutError`: `invalid_client_secret` for an unknown quote or secret.
80
+ * Rejects with a `CheckoutError`: `invalid_client_secret` for an unknown quote or secret,
81
+ * `address_mismatch` for a quote whose address is not `expectedAddress`.
52
82
  */
53
83
  export declare function retrieveQuote(options: RetrieveQuoteOptions): Promise<ClientQuote>;
54
84
  /** The payer-facing status of a quote at `nowSeconds`. */
@@ -1 +1 @@
1
- {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzF;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,SAAS,GACT,MAAM,GACN,YAAY,GACZ,UAAU,GACV,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,CAAC;AAEZ,MAAM,MAAM,iBAAiB,GACzB,uBAAuB,GACvB,cAAc,GACd,eAAe,GACf,kBAAkB,GAClB,WAAW,CAAC;AAEhB,qBAAa,aAAc,SAAQ,KAAK;IAIpC,QAAQ,CAAC,IAAI,EAAE,iBAAiB;IAHlC,SAAkB,IAAI,mBAAmB;gBAG9B,IAAI,EAAE,iBAAiB,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAIzB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,gGAAgG;IAChG,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,IAAI,aAAa,CAAC;IAC1B,gFAAgF;IAChF,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAChE,uDAAuD;IACvD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,qBAAqB;IACrB,OAAO,IAAI,IAAI,CAAC;CACjB;AAKD,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAuBvF;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAWrF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,CAgGxE"}
1
+ {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"AACA,OAAO,EAA6C,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,SAAS,GACT,MAAM,GACN,YAAY,GACZ,UAAU,GACV,UAAU,GACV,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,CAAC;AAEZ,MAAM,MAAM,iBAAiB,GACzB,uBAAuB,GACvB,kBAAkB,GAClB,cAAc,GACd,eAAe,GACf,kBAAkB,GAClB,WAAW,CAAC;AAEhB,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAc,SAAQ,KAAK;IAQpC,QAAQ,CAAC,IAAI,EAAE,iBAAiB;IAPlC,SAAkB,IAAI,mBAAmB;IACzC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAG7B,IAAI,EAAE,iBAAiB,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,oBAAoB;CAMjC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAkBjF;AAED,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAGjG;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,gGAAgG;IAChG,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,IAAI,aAAa,CAAC;IAC1B,gFAAgF;IAChF,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAChE,uDAAuD;IACvD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,qBAAqB;IACrB,OAAO,IAAI,IAAI,CAAC;CACjB;AAKD,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BvF;AASD,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAWrF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,CAqGxE"}
package/dist/checkout.js CHANGED
@@ -1,42 +1,80 @@
1
+ import { getAddress, isAddress, isAddressEqual } from "viem";
1
2
  import { parseClientQuote, quoteIdFromClientSecret } from "./quote.js";
2
3
  export class CheckoutError extends Error {
3
4
  code;
4
5
  name = "CheckoutError";
6
+ /** The failed response's `Request-Id`, when the service answered. */
7
+ requestId;
8
+ /** For `rate_limited`: the seconds to wait before the next read. */
9
+ retryAfter;
5
10
  constructor(code, message, options) {
6
11
  super(message, options);
7
12
  this.code = code;
13
+ this.requestId = options?.requestId;
14
+ this.retryAfter = options?.retryAfter;
8
15
  }
9
16
  }
17
+ /**
18
+ * The error of a failed read of a public view, with the response's `Request-Id` and, on `429`,
19
+ * its `Retry-After`.
20
+ */
21
+ export function responseError(response, notFound) {
22
+ const header = response.headers.get("request-id");
23
+ const requestId = header === null ? {} : { requestId: header };
24
+ if (response.status === 404) {
25
+ return new CheckoutError("invalid_client_secret", notFound, requestId);
26
+ }
27
+ if (response.status === 429) {
28
+ const seconds = Number(response.headers.get("retry-after"));
29
+ return new CheckoutError("rate_limited", "too many status requests", {
30
+ ...requestId,
31
+ ...(Number.isFinite(seconds) && seconds > 0 ? { retryAfter: seconds } : {}),
32
+ });
33
+ }
34
+ return new CheckoutError("api_error", `the payment service answered ${response.status}`, requestId);
35
+ }
36
+ /** The delay before the next read after `failures` failed ones, at least what `error` asks. */
37
+ export function pollDelay(interval, failures, error) {
38
+ const backoff = failures === 0 ? interval : Math.min(interval * 2 ** failures, MAX_BACKOFF);
39
+ return Math.max(backoff, (error?.retryAfter ?? 0) * 1000);
40
+ }
10
41
  const DEFAULT_POLL_INTERVAL = 3000;
11
42
  const MAX_BACKOFF = 30_000;
12
43
  /**
13
44
  * Reads a quote's public view once, as Stripe.js's `retrievePaymentIntent(clientSecret)` does.
14
- * Rejects with a `CheckoutError`: `invalid_client_secret` for an unknown quote or secret.
45
+ * Rejects with a `CheckoutError`: `invalid_client_secret` for an unknown quote or secret,
46
+ * `address_mismatch` for a quote whose address is not `expectedAddress`.
15
47
  */
16
48
  export async function retrieveQuote(options) {
17
49
  const quoteId = quoteIdFromClientSecret(options.clientSecret);
50
+ const expected = expectedAddress(options.expectedAddress);
18
51
  const base = options.apiBase.replace(/\/+$/, "");
19
52
  const url = `${base}/v1/quotes/${quoteId}?client_secret=${encodeURIComponent(options.clientSecret)}`;
20
53
  const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
21
54
  // A simple GET with no custom headers, so the browser sends no CORS preflight.
22
55
  const response = await fetchImpl(url, { cache: "no-store", credentials: "omit" });
23
- if (response.status === 404) {
24
- throw new CheckoutError("invalid_client_secret", "the quote or its client secret is unknown");
25
- }
26
- if (response.status === 429) {
27
- throw new CheckoutError("rate_limited", "too many status requests");
28
- }
29
56
  if (!response.ok) {
30
- throw new CheckoutError("api_error", `the payment service answered ${response.status}`);
57
+ throw responseError(response, "the quote or its client secret is unknown");
31
58
  }
59
+ let quote;
32
60
  try {
33
- return parseClientQuote(await response.json());
61
+ quote = parseClientQuote(await response.json());
34
62
  }
35
63
  catch (cause) {
36
64
  throw new CheckoutError("invalid_response", "unexpected response from the payment service", {
37
65
  cause,
38
66
  });
39
67
  }
68
+ if (!isAddressEqual(getAddress(quote.address), expected)) {
69
+ throw new CheckoutError("address_mismatch", "the quote's address is not the one the merchant expects");
70
+ }
71
+ return quote;
72
+ }
73
+ function expectedAddress(value) {
74
+ if (!isAddress(value, { strict: false })) {
75
+ throw new TypeError("expectedAddress is not an address");
76
+ }
77
+ return getAddress(value);
40
78
  }
41
79
  /** The payer-facing status of a quote at `nowSeconds`. */
42
80
  export function checkoutStatus(quote, nowSeconds) {
@@ -59,6 +97,7 @@ export function checkoutStatus(quote, nowSeconds) {
59
97
  */
60
98
  export function createCheckout(options) {
61
99
  quoteIdFromClientSecret(options.clientSecret);
100
+ expectedAddress(options.expectedAddress);
62
101
  const now = options.now ?? Date.now;
63
102
  const interval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;
64
103
  const listeners = new Set();
@@ -84,6 +123,7 @@ export function createCheckout(options) {
84
123
  status === "error" ||
85
124
  status === "credited" ||
86
125
  status === "rejected" ||
126
+ status === "reversed" ||
87
127
  status === "canceled" ||
88
128
  (status === "expired" && quote?.status === "expired"));
89
129
  }
@@ -99,7 +139,11 @@ export function createCheckout(options) {
99
139
  : new CheckoutError("network_error", "could not reach the payment service", { cause });
100
140
  failures += 1;
101
141
  const quote = state.quote;
102
- if (error.code === "invalid_client_secret") {
142
+ if (error.code === "address_mismatch") {
143
+ // Fail closed: forget the quote, so nothing about its address is shown.
144
+ setState({ status: "error", quote: null, error });
145
+ }
146
+ else if (error.code === "invalid_client_secret") {
103
147
  setState({ status: "error", quote, error });
104
148
  }
105
149
  else {
@@ -121,7 +165,7 @@ export function createCheckout(options) {
121
165
  if (finished()) {
122
166
  return;
123
167
  }
124
- const delay = failures === 0 ? interval : Math.min(interval * 2 ** failures, MAX_BACKOFF);
168
+ const delay = pollDelay(interval, failures, state.error);
125
169
  timer = setTimeout(() => {
126
170
  void refresh().then(schedule);
127
171
  }, delay);
@@ -1 +1 @@
1
- {"version":3,"file":"checkout.js","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"checkout.js","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -7,20 +7,21 @@ export interface PhalaPayOptions {
7
7
  }
8
8
  /**
9
9
  * The browser client, in the shape of Stripe.js: it holds no key, only the service origin, and
10
- * works with the `client_secret` your backend got from `POST /v1/quotes`.
10
+ * works with the `client_secret` your backend got from `POST /v1/quotes` and the `address` its
11
+ * SDK recomputed (`expectedAddress`).
11
12
  *
12
13
  * const pay = new PhalaPay({ apiBase: "https://pay.example.com" });
13
- * const quote = await pay.retrieveQuote(clientSecret);
14
- * const session = pay.checkout(clientSecret);
14
+ * const quote = await pay.retrieveQuote(clientSecret, expectedAddress);
15
+ * const session = pay.checkout(clientSecret, { expectedAddress });
15
16
  * session.subscribe(({ status }) => render(status));
16
17
  */
17
18
  export declare class PhalaPay {
18
19
  #private;
19
20
  readonly apiBase: string;
20
21
  constructor(options: PhalaPayOptions);
21
- /** Reads the quote's public view once. */
22
- retrieveQuote(clientSecret: string): Promise<ClientQuote>;
23
- /** Follows the quote until it is credited, rejected, canceled, or expired. */
24
- checkout(clientSecret: string, options?: Pick<CheckoutOptions, "pollInterval">): CheckoutSession;
22
+ /** Reads the quote's public view once, refusing one whose address is not `expectedAddress`. */
23
+ retrieveQuote(clientSecret: string, expectedAddress: string): Promise<ClientQuote>;
24
+ /** Follows the quote until it is credited, rejected, reversed, canceled, or expired. */
25
+ checkout(clientSecret: string, options: Pick<CheckoutOptions, "expectedAddress" | "pollInterval">): CheckoutSession;
25
26
  }
26
27
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,qBAAa,QAAQ;;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGb,OAAO,EAAE,eAAe;IAKpC,0CAA0C;IAC1C,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIzD,8EAA8E;IAC9E,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,cAAc,CAAM,GAClD,eAAe;CAYnB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,qBAAa,QAAQ;;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGb,OAAO,EAAE,eAAe;IAKpC,+FAA+F;IAC/F,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IASlF,wFAAwF;IACxF,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,iBAAiB,GAAG,cAAc,CAAC,GACjE,eAAe;CAYnB"}