@phala/pay 0.10.10 → 0.11.1

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 CHANGED
@@ -57,6 +57,9 @@ connects a wallet with wagmi, RainbowKit, ConnectKit, or AppKit, pass it in inst
57
57
  3. Credit the account when your webhook endpoint receives `deposit.credited`. `onSuccess` is for the
58
58
  UI only: the browser is not a trusted source of payment.
59
59
 
60
+ To let the payer choose the token and network first and create the quote only when they submit,
61
+ render [`<PaymentElement>`](#the-payment-element) instead.
62
+
60
63
  The component reads `GET /v1/quotes/{id}?client_secret=…` about every three seconds. That endpoint is
61
64
  public (`Access-Control-Allow-Origin: *`) and shows only what the payer needs: the amount, the
62
65
  token, the chain, the deposit address, the EIP-681 payment request, the expiry, and the payment
@@ -154,12 +157,99 @@ export function TopUp(props: { clientSecret: string; expectedAddress: string })
154
157
  While no wallet is connected (`walletClient` is `undefined`), the tab lists the browser's wallets
155
158
  as usual. The QR code and manual tabs do not change.
156
159
 
160
+ ### The payment element
161
+
162
+ `<PaymentElement>` is the whole payment in one component, like Stripe's Payment Element with a
163
+ deferred intent: it offers your tokens and networks, creates nothing until the payer submits, then
164
+ calls your `createQuote` with the chosen option and an idempotency key, and shows that quote's
165
+ `<Checkout>`. Your backend creates the quote with its API key, for the chosen `chain_id` and
166
+ `asset`, with that key as its `Idempotency-Key`, and returns its checkout params
167
+ (`pay.checkoutParams(quote)` in `@phala/pay-server`).
168
+
169
+ ```tsx
170
+ import { paymentOptions } from "@phala/pay";
171
+ import { PaymentElement } from "@phala/pay-react";
172
+
173
+ <PaymentElement
174
+ options={paymentOptions(config.assets)} // GET /v1/config's assets, from your backend
175
+ amount={2500} // minor units of `currency` (default "usd"): "Pay $25.00 in USDC"
176
+ createQuote={(option, { idempotencyKey }) =>
177
+ api.createTopUp({ amount: 2500, chain_id: option.chain_id, asset: option.asset, idempotencyKey })
178
+ }
179
+ createDepositAddress={() => api.depositAddress()} // optional: "Use a deposit address"
180
+ apiBase="https://pay.example.com"
181
+ renderDetail={(option) => (option.asset === "usdc" ? "$1.00" : "Market rate")}
182
+ onSuccess={() => router.refresh()}
183
+ />;
184
+ ```
185
+
186
+ - `createQuote(option, { idempotencyKey })` is called once per submit, with the very object of
187
+ `options` the payer chose; a submit while one is pending is ignored. It resolves with
188
+ `{ clientSecret, expectedAddress, apiBase? }`; `apiBase` falls back to the prop. A rejection
189
+ shows "Could not start the payment. Try again." and calls `onError(error)`. To show your own
190
+ reason in that alert instead, pass `errorMessage(cause)`: a non-empty string replaces the text,
191
+ and `null` or `undefined` keeps it. It applies to `createDepositAddress` too.
192
+ - Create the quote with `idempotencyKey` as its `Idempotency-Key`, not an order id of yours: the
193
+ key is a new random UUID for each quote the payer asks for (another option or amount, or a new
194
+ quote after going back from one) and the same only when they retry after `createQuote` failed
195
+ with the same option, `amount`, and `currency`, so that a retry returns the quote a lost response
196
+ created. One key per order would answer another option with `400 idempotency_key_reused`, and a
197
+ new quote with the expired one. If your quote varies by anything else between attempts (a field
198
+ of your own form, say), pass it as `amount` or give the element a new `key` when it changes, or
199
+ a retry would reuse a key with another body.
200
+ - After the quote: the checkout, and a way back to the options ("Choose another token or
201
+ network") until a payment is on its way, then "Start over" if it does not complete or "Start a
202
+ new payment" once credited.
203
+ - `createDepositAddress(option)` adds "Use a deposit address", for payers sending from an exchange
204
+ or any wallet: it resolves with the deposit address's `address` and `networks` and, to follow its
205
+ payments, `clientSecret`; the element shows `<DepositAddress>` at the chosen option. Under the
206
+ link it states the chosen option's per-transfer limits when it carries them, as a
207
+ `GET /v1/config` asset does: "Send at least $5.00 and up to 10,100 USDC per transfer on Base from
208
+ an exchange or another wallet." Without them it says the address shows its limits.
209
+ - Your own button: pass `hideSubmit` and an `id`, and give your button `type="submit" form={id}`
210
+ (or call `requestSubmit()` on the form with that id). The element is a `<form>`: do not nest it
211
+ in another one.
212
+ - `value` and `onOptionChange` control the choice (`defaultValue` sets the first one otherwise);
213
+ `onChange({ step, checkout })` reports each step (`options`, `creating`, `checkout`,
214
+ `deposit_address`) and, while paying, each checkout status change; `onSuccess`, `onExpire`,
215
+ `onWalletError`, `walletClient`, `pollInterval`, and `appearance` are `<Checkout>`'s.
216
+
217
+ ### Payment options
218
+
219
+ `<PaymentOptionPicker>` is the element's choice on its own, as exchanges and wallets ask for it:
220
+ a "Token" field, then a "Network" field listing only the networks that take the chosen token,
221
+ side by side where the container is 480px or wider and stacked below that. Each is a select-only
222
+ combobox (WAI-ARIA Authoring Practices): a quiet field showing the chosen item's icon and name,
223
+ whose listbox opens under it (over it where there is more room above), by pointer or keyboard
224
+ (arrows, Home, End, Page Up and Down, typing the name, Enter or Space to choose, Escape or a
225
+ press elsewhere to close); its label focuses it. Where the browser has popovers, the open listbox
226
+ is in the top layer, so a card or modal with `overflow: hidden` does not clip it, while it stays
227
+ inside your dialog in the document (a modal library does not take a press on it for one outside
228
+ the dialog). Changing the token keeps the network
229
+ when it takes the new token, else takes the first that does. A test network shows a muted
230
+ "Testnet" after its name; `renderDetail(option)` is one muted line under the fields, about the
231
+ chosen option. Both fields show even with one option. It is controlled (`value`, `onChange`), and
232
+ `onChange` receives the very object of `options` the payer chose; `label` (default "Pay with")
233
+ names the group for assistive technology.
234
+
235
+ ```tsx
236
+ <PaymentOptionPicker options={options} value={option} onChange={setOption} label="Pay with" />
237
+ ```
238
+
239
+ `paymentOptions(source)` builds the options, in order and each once: from a flat list such as
240
+ `GET /v1/config`'s `assets` (the same objects), or from networks with their tokens such as a
241
+ deposit address's `networks` (each option then carries its `network` and `token`).
242
+ `samePaymentOption(a, b)` compares by chain and case-insensitive asset code;
243
+ `paymentOptionLabel(option)` is `USDC · Ethereum`; `assetName("usdc")` is `USD Coin`,
244
+ `assetSymbol` the upper-case code, `networkName(chainId)` the chain's name, `isTestnet(chainId)`
245
+ whether it is a known test network, and `transactionUrl` / `addressUrl` its block explorer links.
246
+
157
247
  ### A customer's deposit address
158
248
 
159
249
  For top-ups within your deposit limits, your backend creates the customer's persistent deposit address with
160
250
  `POST /v1/deposit_addresses` (its API key) and passes its `address` and `networks` to the page:
161
- one address for all supported tokens and networks. `<DepositAddress>` lets the payer pick a
162
- network and a token and shows a QR code of that token's transfer request, the token contract, and
251
+ one address for all supported tokens and networks. `<DepositAddress>` lets the payer pick a token
252
+ and its network (the payment options' two fields, shown even when there is only one of each) and shows a QR code of that token's transfer request, the token contract, and
163
253
  the address to copy, with the selected token’s per-transfer USD credit minimum (`min_amount`,
164
254
  in cents) and token maximum (`max_deposit_atomic`). A positive `min_deposit_atomic` adds the
165
255
  independent token minimum; a zero atomic minimum is omitted. Atomic-unit strings are formatted
@@ -223,14 +313,14 @@ the theme (or map the colors) that suits its background.
223
313
 
224
314
  | CSS custom property | Light default | Dark default | Use |
225
315
  | ------------------------------- | ----------------- | ------------ | --------------------------------------------------- |
226
- | `--pp-color-surface` | `#f4f4f5` | `#18181b` | Status row, loading placeholder, copy button hover |
316
+ | `--pp-color-surface` | `#f4f4f5` | `#18181b` | Status row, placeholders, listboxes, copy hover |
227
317
  | `--pp-color-text` | `#18181b` | `#fafafa` | Text |
228
318
  | `--pp-color-text-muted` | `#52525b` | `#a1a1aa` | Labels, hints, in-progress status icon |
229
319
  | `--pp-color-border` | `#e4e4e7` | `#27272a` | Dividers and the QR code's frame |
230
- | `--pp-color-input` | `#8a8a93` | `#71717a` | Edges of the network and token choices |
320
+ | `--pp-color-input` | `#8a8a93` | `#71717a` | Edges of the token and network fields |
231
321
  | `--pp-color-primary` | `#171717` | `#fafafa` | Pay button |
232
322
  | `--pp-color-primary-foreground` | `#fafafa` | `#18181b` | Text on the primary color |
233
- | `--pp-color-selected` | primary | primary | Selected tab's underline, checked choice's border |
323
+ | `--pp-color-selected` | primary | primary | Selected tab's underline |
234
324
  | `--pp-color-focus` | `#2563eb` | `#60a5fa` | Keyboard focus ring |
235
325
  | `--pp-color-success` | `#15803d` | `#4ade80` | Credited icon |
236
326
  | `--pp-color-warning` | `#b45309` | `#fbbf24` | "Test mode" |
@@ -239,7 +329,7 @@ the theme (or map the colors) that suits its background.
239
329
  | `--pp-font-family-mono` | `ui-monospace, …` | same | Addresses, amounts to copy, hashes |
240
330
  | `--pp-font-size` | `14px` | `14px` | Root text size; other text sizes are relative to it |
241
331
  | `--pp-radius` | `8px` | `8px` | Corner radius of controls and the status row |
242
- | `--pp-control-height` | `44px` | `44px` | Minimum height of the pay button, tabs, and choices |
332
+ | `--pp-control-height` | `44px` | `44px` | Minimum height of buttons, tabs, and fields |
243
333
 
244
334
  Import the static stylesheet once in your application entry point:
245
335
 
@@ -287,13 +377,15 @@ group by group with what their wallet shows; selecting and copying it gives the
287
377
  spaces. The QR code keeps the standard quiet zone of four modules inside 8px of white, so it scans
288
378
  on a dark page too. The status row's text stays the body color on a neutral surface; its icon
289
379
  carries the tone. Transitions and the loading placeholder's pulse stop under
290
- `prefers-reduced-motion: reduce`, and the selected choice and tab keep a system color in
291
- forced-colors mode.
380
+ `prefers-reduced-motion: reduce`, and the selected tab and a listbox's active option keep a system
381
+ color in forced-colors mode.
292
382
 
293
383
  ### Content Security Policy
294
384
 
295
- The components inject no style elements or style attributes. Serve the bundled CSS from your
296
- own origin with `style-src 'self'`; no `unsafe-inline` style permission is needed. Allow the
385
+ The components inject no style elements or style attributes in their markup. Serve the bundled
386
+ CSS from your own origin with `style-src 'self'`; no `unsafe-inline` style permission is needed.
387
+ An open token or network listbox is placed beside its field through the CSSOM
388
+ (`element.style.setProperty`), which that policy allows. Allow the
297
389
  payment service origin in `connect-src` for public status reads and `img-src data:` for browser
298
390
  wallet icons. Configure script and font sources for your application's own bundles.
299
391
 
@@ -340,7 +432,7 @@ holding less of the token than that sends nothing: `WalletError` with `code`
340
432
 
341
433
  ## React components
342
434
 
343
- Install `@phala/pay-react` for `Checkout`, `DepositAddress`, hooks, icons, and styles:
435
+ Install `@phala/pay-react` for `PaymentElement`, `Checkout`, `DepositAddress`, `PaymentOptionPicker`, hooks, icons, and styles:
344
436
 
345
437
  ```sh
346
438
  npm install @phala/pay-react react react-dom @phala/pay viem
package/dist/chains.d.ts CHANGED
@@ -4,6 +4,10 @@ export declare function networkIconName(chainId: number): "ethereum" | "base" |
4
4
  export declare function knownChain(chainId: number): Chain | undefined;
5
5
  /** A human name for the network, for example `Sepolia`. */
6
6
  export declare function networkName(chainId: number): string;
7
+ /** Whether the chain is a known test network, such as Sepolia; false for an unknown chain. */
8
+ export declare function isTestnet(chainId: number): boolean;
7
9
  /** The block explorer link for a transaction, when the chain is known. */
8
10
  export declare function transactionUrl(chainId: number, hash: string): string | undefined;
11
+ /** The block explorer link for an address or token contract, when the chain is known. */
12
+ export declare function addressUrl(chainId: number, address: string): string | undefined;
9
13
  //# sourceMappingURL=chains.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAWlC,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAEhF;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAE7D;AAED,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhF"}
1
+ {"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAWlC,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAEhF;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAE7D;AAED,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,8FAA8F;AAC9F,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEhF;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E"}
package/dist/chains.js CHANGED
@@ -17,9 +17,20 @@ export function knownChain(chainId) {
17
17
  export function networkName(chainId) {
18
18
  return knownChain(chainId)?.name ?? `Chain ${chainId}`;
19
19
  }
20
+ /** Whether the chain is a known test network, such as Sepolia; false for an unknown chain. */
21
+ export function isTestnet(chainId) {
22
+ return knownChain(chainId)?.testnet === true;
23
+ }
20
24
  /** The block explorer link for a transaction, when the chain is known. */
21
25
  export function transactionUrl(chainId, hash) {
26
+ return explorerUrl(chainId, "tx", hash);
27
+ }
28
+ /** The block explorer link for an address or token contract, when the chain is known. */
29
+ export function addressUrl(chainId, address) {
30
+ return explorerUrl(chainId, "address", address);
31
+ }
32
+ function explorerUrl(chainId, kind, value) {
22
33
  const explorer = knownChain(chainId)?.blockExplorers?.default.url;
23
- return explorer === undefined ? undefined : `${explorer}/tx/${hash}`;
34
+ return explorer === undefined ? undefined : `${explorer}/${kind}/${value}`;
24
35
  }
25
36
  //# sourceMappingURL=chains.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chains.js","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElE,0FAA0F;AAC1F,MAAM,MAAM,GAA2D;IACrE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE;IACpC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE;IACpC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE;IAC7B,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE;CACrC,CAAC;AAEF,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC;AACjE,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,IAAI,SAAS,OAAO,EAAE,CAAC;AACzD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY;IAC1D,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC;IAClE,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,OAAO,IAAI,EAAE,CAAC;AACvE,CAAC"}
1
+ {"version":3,"file":"chains.js","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElE,0FAA0F;AAC1F,MAAM,MAAM,GAA2D;IACrE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE;IACpC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE;IACpC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE;IAC7B,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE;CACrC,CAAC;AAEF,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC;AACjE,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,IAAI,SAAS,OAAO,EAAE,CAAC;AACzD,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAC/C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY;IAC1D,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAe;IACzD,OAAO,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,WAAW,CAAC,OAAe,EAAE,IAAsB,EAAE,KAAa;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC;IAClE,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;AAC7E,CAAC"}
@@ -2,6 +2,10 @@ import type { ICON_SVG } from "./icon-svg.js";
2
2
  export type IconName = keyof typeof ICON_SVG;
3
3
  /** Case-insensitive token symbol; unrecognized symbols use a neutral monogram. */
4
4
  export declare function assetIconName(asset: string): IconName | undefined;
5
+ /** The token's symbol as payers read it, for example `USDC` for `usdc`. */
6
+ export declare function assetSymbol(asset: string): string;
7
+ /** The token's full name, for example `USD Coin`, or its symbol when the SDK does not know it. */
8
+ export declare function assetName(asset: string): string;
5
9
  /** First character for a neutral fallback, including an empty-symbol placeholder. */
6
10
  export declare function iconMonogram(label: string): string;
7
11
  //# sourceMappingURL=icon-names.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"icon-names.d.ts","sourceRoot":"","sources":["../src/icon-names.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,QAAQ,CAAC;AAE7C,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAQjE;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
1
+ {"version":3,"file":"icon-names.d.ts","sourceRoot":"","sources":["../src/icon-names.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,QAAQ,CAAC;AAe7C,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAEjE;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -1,12 +1,25 @@
1
+ /** The tokens the SDK knows, by lowercase code: their icon and full name. */
2
+ const ASSETS = {
3
+ usdc: { icon: "usdc", name: "USD Coin" },
4
+ usdt: { icon: "usdt", name: "Tether USD" },
5
+ pha: { icon: "pha", name: "Phala Network" },
6
+ eth: { icon: "eth", name: "Ether" },
7
+ };
8
+ function knownAsset(asset) {
9
+ const code = asset.trim().toLowerCase();
10
+ return Object.hasOwn(ASSETS, code) ? ASSETS[code] : undefined;
11
+ }
1
12
  /** Case-insensitive token symbol; unrecognized symbols use a neutral monogram. */
2
13
  export function assetIconName(asset) {
3
- switch (asset.trim().toLowerCase()) {
4
- case "usdc": return "usdc";
5
- case "usdt": return "usdt";
6
- case "pha": return "pha";
7
- case "eth": return "eth";
8
- default: return undefined;
9
- }
14
+ return knownAsset(asset)?.icon;
15
+ }
16
+ /** The token's symbol as payers read it, for example `USDC` for `usdc`. */
17
+ export function assetSymbol(asset) {
18
+ return asset.trim().toUpperCase();
19
+ }
20
+ /** The token's full name, for example `USD Coin`, or its symbol when the SDK does not know it. */
21
+ export function assetName(asset) {
22
+ return knownAsset(asset)?.name ?? assetSymbol(asset);
10
23
  }
11
24
  /** First character for a neutral fallback, including an empty-symbol placeholder. */
12
25
  export function iconMonogram(label) {
@@ -1 +1 @@
1
- {"version":3,"file":"icon-names.js","sourceRoot":"","sources":["../src/icon-names.ts"],"names":[],"mappings":"AAIA,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC;QAC3B,KAAK,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC;QAC3B,KAAK,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC;QACzB,KAAK,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC;QACzB,OAAO,CAAC,CAAC,OAAO,SAAS,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC;AAC3D,CAAC"}
1
+ {"version":3,"file":"icon-names.js","sourceRoot":"","sources":["../src/icon-names.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,MAAM,MAAM,GAA+D;IACzE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;IACxC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE;IAC1C,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE;IAC3C,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE;CACpC,CAAC;AAEF,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;AACjC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACpC,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;AACvD,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC;AAC3D,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  export { CheckoutError, checkoutStatus, pollDelay, createCheckout, retrieveQuote, type CheckoutErrorCode, type CheckoutErrorOptions, type CheckoutOptions, type CheckoutSession, type CheckoutState, type CheckoutStatus, type RetrieveQuoteOptions, } from "./checkout.js";
2
2
  export { PhalaPay, type PhalaPayOptions } from "./client.js";
3
3
  export { depositAddressIdFromClientSecret, parseClientDepositAddress, retrieveDepositAddress, type ClientDepositAddress, type ClientDepositAddressNetwork, type DepositAddressPayment, type RetrieveDepositAddressOptions, } from "./deposit-address.js";
4
- export { knownChain, networkName, transactionUrl, networkIconName } from "./chains.js";
4
+ export { addressUrl, isTestnet, knownChain, networkName, transactionUrl, networkIconName } from "./chains.js";
5
5
  export { formatAmount, formatCountdown, formatMinorAmount, formatTokenAmount, formatUnitsGrouped, formatWait, tokenAmount } from "./format.js";
6
6
  export { depositAddressTransfer, parseTransferUri, quoteTransfer, type DepositAddressAsset, type DepositAddressDetails, type DepositAddressNetwork, type TokenTransfer, type TransferRequest, } from "./payment.js";
7
7
  export { parseClientQuote, quoteIdFromClientSecret, type ClientQuote } from "./quote.js";
8
- export { assetIconName, iconMonogram, type IconName } from "./icon-names.js";
8
+ export { assetIconName, assetName, assetSymbol, iconMonogram, type IconName } from "./icon-names.js";
9
+ export { paymentOptionLabel, paymentOptions, samePaymentOption, type NetworkPaymentOption, type PaymentNetwork, type PaymentOption, } from "./options.js";
9
10
  export { INJECTED_WALLET_UUID, WalletError, payWithWallet, watchWallets, type EthereumProvider, type Wallet, type WalletErrorCode, type WalletInfo, } from "./wallet.js";
10
11
  export { networkIcon, assetIcon } from "./icons.js";
11
12
  export type { CheckoutParams } from "./shared/checkout-params.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,SAAS,EACT,cAAc,EACd,aAAa,EACb,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,GACnC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/I,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,SAAS,EACT,cAAc,EACd,aAAa,EACb,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,GACnC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/I,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrG,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  export { CheckoutError, checkoutStatus, pollDelay, createCheckout, retrieveQuote, } from "./checkout.js";
2
2
  export { PhalaPay } from "./client.js";
3
3
  export { depositAddressIdFromClientSecret, parseClientDepositAddress, retrieveDepositAddress, } from "./deposit-address.js";
4
- export { knownChain, networkName, transactionUrl, networkIconName } from "./chains.js";
4
+ export { addressUrl, isTestnet, knownChain, networkName, transactionUrl, networkIconName } from "./chains.js";
5
5
  export { formatAmount, formatCountdown, formatMinorAmount, formatTokenAmount, formatUnitsGrouped, formatWait, tokenAmount } from "./format.js";
6
6
  export { depositAddressTransfer, parseTransferUri, quoteTransfer, } from "./payment.js";
7
7
  export { parseClientQuote, quoteIdFromClientSecret } from "./quote.js";
8
- export { assetIconName, iconMonogram } from "./icon-names.js";
8
+ export { assetIconName, assetName, assetSymbol, iconMonogram } from "./icon-names.js";
9
+ export { paymentOptionLabel, paymentOptions, samePaymentOption, } from "./options.js";
9
10
  export { INJECTED_WALLET_UUID, WalletError, payWithWallet, watchWallets, } from "./wallet.js";
10
11
  export { networkIcon, assetIcon } from "./icons.js";
11
12
  export { submitTransaction } from "./transactions.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,SAAS,EACT,cAAc,EACd,aAAa,GAQd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAwB,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,sBAAsB,GAKvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/I,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GAMd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAoB,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAiB,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,GAKb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,iBAAiB,EAAiC,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,SAAS,EACT,cAAc,EACd,aAAa,GAQd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAwB,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,sBAAsB,GAKvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/I,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GAMd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAoB,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAiB,MAAM,iBAAiB,CAAC;AACrG,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,GAIlB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,YAAY,GAKb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,iBAAiB,EAAiC,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,30 @@
1
+ /** A token on a network that a payer can pay with, for example `usdc` on chain 8453. */
2
+ export interface PaymentOption {
3
+ chain_id: number;
4
+ /** The token's code, for example `usdc`. */
5
+ asset: string;
6
+ }
7
+ /** A network with the tokens it takes, as a deposit address's `networks` list them. */
8
+ export interface PaymentNetwork {
9
+ chain_id: number;
10
+ assets: readonly {
11
+ asset: string;
12
+ }[];
13
+ }
14
+ /** One token of a `PaymentNetwork`, with the network and token objects it came from. */
15
+ export type NetworkPaymentOption<N extends PaymentNetwork> = PaymentOption & {
16
+ network: N;
17
+ token: N["assets"][number];
18
+ };
19
+ /**
20
+ * The options to offer, in the order given and each once (the first of any duplicate): either a
21
+ * flat list such as `GET /v1/config`'s `assets`, returned as the same objects, or networks with
22
+ * their tokens such as a deposit address's `networks`, flattened network by network.
23
+ */
24
+ export declare function paymentOptions<T extends PaymentOption>(options: readonly T[]): T[];
25
+ export declare function paymentOptions<N extends PaymentNetwork>(networks: readonly N[]): NetworkPaymentOption<N>[];
26
+ /** Whether `a` and `b` are the same token on the same network; asset codes are case-insensitive. */
27
+ export declare function samePaymentOption(a: PaymentOption | null | undefined, b: PaymentOption | null | undefined): boolean;
28
+ /** The option as one line of text, for example `USDC · Ethereum`. */
29
+ export declare function paymentOptionLabel(option: PaymentOption): string;
30
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAGA,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtC;AAED,wFAAwF;AACxF,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,cAAc,IAAI,aAAa,GAAG;IAC3E,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACpF,wBAAgB,cAAc,CAAC,CAAC,SAAS,cAAc,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;AAY5G,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAOnH;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAEhE"}
@@ -0,0 +1,20 @@
1
+ import { networkName } from "./chains.js";
2
+ import { assetSymbol } from "./icon-names.js";
3
+ export function paymentOptions(source) {
4
+ const flat = source.flatMap((each) => "assets" in each
5
+ ? each.assets.map((token) => ({ chain_id: each.chain_id, asset: token.asset, network: each, token }))
6
+ : [each]);
7
+ return flat.filter((option, index) => flat.findIndex((other) => samePaymentOption(option, other)) === index);
8
+ }
9
+ /** Whether `a` and `b` are the same token on the same network; asset codes are case-insensitive. */
10
+ export function samePaymentOption(a, b) {
11
+ return (a != null &&
12
+ b != null &&
13
+ a.chain_id === b.chain_id &&
14
+ a.asset.trim().toLowerCase() === b.asset.trim().toLowerCase());
15
+ }
16
+ /** The option as one line of text, for example `USDC · Ethereum`. */
17
+ export function paymentOptionLabel(option) {
18
+ return `${assetSymbol(option.asset)} · ${networkName(option.chain_id)}`;
19
+ }
20
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA4B9C,MAAM,UAAU,cAAc,CAC5B,MAAmD;IAEnD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACnC,QAAQ,IAAI,IAAI;QACd,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACrG,CAAC,CAAC,CAAC,IAAI,CAAC,CACX,CAAC;IACF,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;AAC/G,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,CAAmC,EAAE,CAAmC;IACxG,OAAO,CACL,CAAC,IAAI,IAAI;QACT,CAAC,IAAI,IAAI;QACT,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;QACzB,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAC9D,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC1E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phala/pay",
3
- "version": "0.10.10",
3
+ "version": "0.11.1",
4
4
  "description": "Phala Pay framework-free browser client",
5
5
  "keywords": [
6
6
  "crypto",
package/src/chains.ts CHANGED
@@ -23,8 +23,22 @@ export function networkName(chainId: number): string {
23
23
  return knownChain(chainId)?.name ?? `Chain ${chainId}`;
24
24
  }
25
25
 
26
+ /** Whether the chain is a known test network, such as Sepolia; false for an unknown chain. */
27
+ export function isTestnet(chainId: number): boolean {
28
+ return knownChain(chainId)?.testnet === true;
29
+ }
30
+
26
31
  /** The block explorer link for a transaction, when the chain is known. */
27
32
  export function transactionUrl(chainId: number, hash: string): string | undefined {
33
+ return explorerUrl(chainId, "tx", hash);
34
+ }
35
+
36
+ /** The block explorer link for an address or token contract, when the chain is known. */
37
+ export function addressUrl(chainId: number, address: string): string | undefined {
38
+ return explorerUrl(chainId, "address", address);
39
+ }
40
+
41
+ function explorerUrl(chainId: number, kind: "tx" | "address", value: string): string | undefined {
28
42
  const explorer = knownChain(chainId)?.blockExplorers?.default.url;
29
- return explorer === undefined ? undefined : `${explorer}/tx/${hash}`;
43
+ return explorer === undefined ? undefined : `${explorer}/${kind}/${value}`;
30
44
  }
package/src/icon-names.ts CHANGED
@@ -2,19 +2,35 @@ import type { ICON_SVG } from "./icon-svg.js";
2
2
 
3
3
  export type IconName = keyof typeof ICON_SVG;
4
4
 
5
+ /** The tokens the SDK knows, by lowercase code: their icon and full name. */
6
+ const ASSETS: Readonly<Record<string, { icon: IconName; name: string }>> = {
7
+ usdc: { icon: "usdc", name: "USD Coin" },
8
+ usdt: { icon: "usdt", name: "Tether USD" },
9
+ pha: { icon: "pha", name: "Phala Network" },
10
+ eth: { icon: "eth", name: "Ether" },
11
+ };
12
+
13
+ function knownAsset(asset: string) {
14
+ const code = asset.trim().toLowerCase();
15
+ return Object.hasOwn(ASSETS, code) ? ASSETS[code] : undefined;
16
+ }
17
+
5
18
  /** Case-insensitive token symbol; unrecognized symbols use a neutral monogram. */
6
19
  export function assetIconName(asset: string): IconName | undefined {
7
- switch (asset.trim().toLowerCase()) {
8
- case "usdc": return "usdc";
9
- case "usdt": return "usdt";
10
- case "pha": return "pha";
11
- case "eth": return "eth";
12
- default: return undefined;
13
- }
20
+ return knownAsset(asset)?.icon;
21
+ }
22
+
23
+ /** The token's symbol as payers read it, for example `USDC` for `usdc`. */
24
+ export function assetSymbol(asset: string): string {
25
+ return asset.trim().toUpperCase();
26
+ }
27
+
28
+ /** The token's full name, for example `USD Coin`, or its symbol when the SDK does not know it. */
29
+ export function assetName(asset: string): string {
30
+ return knownAsset(asset)?.name ?? assetSymbol(asset);
14
31
  }
15
32
 
16
33
  /** First character for a neutral fallback, including an empty-symbol placeholder. */
17
34
  export function iconMonogram(label: string): string {
18
35
  return Array.from(label.trim())[0]?.toUpperCase() ?? "?";
19
36
  }
20
-
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ export {
22
22
  type DepositAddressPayment,
23
23
  type RetrieveDepositAddressOptions,
24
24
  } from "./deposit-address.js";
25
- export { knownChain, networkName, transactionUrl, networkIconName } from "./chains.js";
25
+ export { addressUrl, isTestnet, knownChain, networkName, transactionUrl, networkIconName } from "./chains.js";
26
26
  export { formatAmount, formatCountdown, formatMinorAmount, formatTokenAmount, formatUnitsGrouped, formatWait, tokenAmount } from "./format.js";
27
27
  export {
28
28
  depositAddressTransfer,
@@ -35,7 +35,15 @@ export {
35
35
  type TransferRequest,
36
36
  } from "./payment.js";
37
37
  export { parseClientQuote, quoteIdFromClientSecret, type ClientQuote } from "./quote.js";
38
- export { assetIconName, iconMonogram, type IconName } from "./icon-names.js";
38
+ export { assetIconName, assetName, assetSymbol, iconMonogram, type IconName } from "./icon-names.js";
39
+ export {
40
+ paymentOptionLabel,
41
+ paymentOptions,
42
+ samePaymentOption,
43
+ type NetworkPaymentOption,
44
+ type PaymentNetwork,
45
+ type PaymentOption,
46
+ } from "./options.js";
39
47
  export {
40
48
  INJECTED_WALLET_UUID,
41
49
  WalletError,
package/src/options.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { networkName } from "./chains.js";
2
+ import { assetSymbol } from "./icon-names.js";
3
+
4
+ /** A token on a network that a payer can pay with, for example `usdc` on chain 8453. */
5
+ export interface PaymentOption {
6
+ chain_id: number;
7
+ /** The token's code, for example `usdc`. */
8
+ asset: string;
9
+ }
10
+
11
+ /** A network with the tokens it takes, as a deposit address's `networks` list them. */
12
+ export interface PaymentNetwork {
13
+ chain_id: number;
14
+ assets: readonly { asset: string }[];
15
+ }
16
+
17
+ /** One token of a `PaymentNetwork`, with the network and token objects it came from. */
18
+ export type NetworkPaymentOption<N extends PaymentNetwork> = PaymentOption & {
19
+ network: N;
20
+ token: N["assets"][number];
21
+ };
22
+
23
+ /**
24
+ * The options to offer, in the order given and each once (the first of any duplicate): either a
25
+ * flat list such as `GET /v1/config`'s `assets`, returned as the same objects, or networks with
26
+ * their tokens such as a deposit address's `networks`, flattened network by network.
27
+ */
28
+ export function paymentOptions<T extends PaymentOption>(options: readonly T[]): T[];
29
+ export function paymentOptions<N extends PaymentNetwork>(networks: readonly N[]): NetworkPaymentOption<N>[];
30
+ export function paymentOptions(
31
+ source: readonly (PaymentOption | PaymentNetwork)[],
32
+ ): (PaymentOption | NetworkPaymentOption<PaymentNetwork>)[] {
33
+ const flat = source.flatMap((each) =>
34
+ "assets" in each
35
+ ? each.assets.map((token) => ({ chain_id: each.chain_id, asset: token.asset, network: each, token }))
36
+ : [each],
37
+ );
38
+ return flat.filter((option, index) => flat.findIndex((other) => samePaymentOption(option, other)) === index);
39
+ }
40
+
41
+ /** Whether `a` and `b` are the same token on the same network; asset codes are case-insensitive. */
42
+ export function samePaymentOption(a: PaymentOption | null | undefined, b: PaymentOption | null | undefined): boolean {
43
+ return (
44
+ a != null &&
45
+ b != null &&
46
+ a.chain_id === b.chain_id &&
47
+ a.asset.trim().toLowerCase() === b.asset.trim().toLowerCase()
48
+ );
49
+ }
50
+
51
+ /** The option as one line of text, for example `USDC · Ethereum`. */
52
+ export function paymentOptionLabel(option: PaymentOption): string {
53
+ return `${assetSymbol(option.asset)} · ${networkName(option.chain_id)}`;
54
+ }