@phala/pay 0.10.10 → 0.11.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 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,95 @@ 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)`; show your own
190
+ message from there.
191
+ - Create the quote with `idempotencyKey` as its `Idempotency-Key`, not an order id of yours: the
192
+ key is a new random UUID for each quote the payer asks for (another option or amount, or a new
193
+ quote after going back from one) and the same only when they retry after `createQuote` failed
194
+ with the same option, `amount`, and `currency`, so that a retry returns the quote a lost response
195
+ created. One key per order would answer another option with `400 idempotency_key_reused`, and a
196
+ new quote with the expired one. If your quote varies by anything else between attempts (a field
197
+ of your own form, say), pass it as `amount` or give the element a new `key` when it changes, or
198
+ a retry would reuse a key with another body.
199
+ - After the quote: the checkout, and a way back to the options ("Choose another token or
200
+ network") until a payment is on its way, then "Start over" if it does not complete or "Start a
201
+ new payment" once credited.
202
+ - `createDepositAddress(option)` adds "Use a deposit address", for payers sending from an exchange
203
+ or any wallet: it resolves with the deposit address's `address` and `networks` and, to follow its
204
+ payments, `clientSecret`; the element shows `<DepositAddress>` at the chosen option.
205
+ - Your own button: pass `hideSubmit` and an `id`, and give your button `type="submit" form={id}`
206
+ (or call `requestSubmit()` on the form with that id). The element is a `<form>`: do not nest it
207
+ in another one.
208
+ - `value` and `onOptionChange` control the choice (`defaultValue` sets the first one otherwise);
209
+ `onChange({ step, checkout })` reports each step (`options`, `creating`, `checkout`,
210
+ `deposit_address`) and, while paying, each checkout status change; `onSuccess`, `onExpire`,
211
+ `onWalletError`, `walletClient`, `pollInterval`, and `appearance` are `<Checkout>`'s.
212
+
213
+ ### Payment options
214
+
215
+ `<PaymentOptionPicker>` is the element's choice on its own, as exchanges and wallets ask for it:
216
+ a "Token" field, then a "Network" field listing only the networks that take the chosen token,
217
+ side by side where the container is 480px or wider and stacked below that. Each is a select-only
218
+ combobox (WAI-ARIA Authoring Practices): a quiet field showing the chosen item's icon and name,
219
+ whose listbox opens under it (over it where there is more room above), by pointer or keyboard
220
+ (arrows, Home, End, Page Up and Down, typing the name, Enter or Space to choose, Escape or a
221
+ press elsewhere to close); its label focuses it. Where the browser has popovers, the open listbox
222
+ is in the top layer, so a card or modal with `overflow: hidden` does not clip it, while it stays
223
+ inside your dialog in the document (a modal library does not take a press on it for one outside
224
+ the dialog). Changing the token keeps the network
225
+ when it takes the new token, else takes the first that does. A test network shows a muted
226
+ "Testnet" after its name; `renderDetail(option)` is one muted line under the fields, about the
227
+ chosen option. Both fields show even with one option. It is controlled (`value`, `onChange`), and
228
+ `onChange` receives the very object of `options` the payer chose; `label` (default "Pay with")
229
+ names the group for assistive technology.
230
+
231
+ ```tsx
232
+ <PaymentOptionPicker options={options} value={option} onChange={setOption} label="Pay with" />
233
+ ```
234
+
235
+ `paymentOptions(source)` builds the options, in order and each once: from a flat list such as
236
+ `GET /v1/config`'s `assets` (the same objects), or from networks with their tokens such as a
237
+ deposit address's `networks` (each option then carries its `network` and `token`).
238
+ `samePaymentOption(a, b)` compares by chain and case-insensitive asset code;
239
+ `paymentOptionLabel(option)` is `USDC · Ethereum`; `assetName("usdc")` is `USD Coin`,
240
+ `assetSymbol` the upper-case code, `networkName(chainId)` the chain's name, `isTestnet(chainId)`
241
+ whether it is a known test network, and `transactionUrl` / `addressUrl` its block explorer links.
242
+
157
243
  ### A customer's deposit address
158
244
 
159
245
  For top-ups within your deposit limits, your backend creates the customer's persistent deposit address with
160
246
  `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
247
+ one address for all supported tokens and networks. `<DepositAddress>` lets the payer pick a token
248
+ 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
249
  the address to copy, with the selected token’s per-transfer USD credit minimum (`min_amount`,
164
250
  in cents) and token maximum (`max_deposit_atomic`). A positive `min_deposit_atomic` adds the
165
251
  independent token minimum; a zero atomic minimum is omitted. Atomic-unit strings are formatted
@@ -223,14 +309,14 @@ the theme (or map the colors) that suits its background.
223
309
 
224
310
  | CSS custom property | Light default | Dark default | Use |
225
311
  | ------------------------------- | ----------------- | ------------ | --------------------------------------------------- |
226
- | `--pp-color-surface` | `#f4f4f5` | `#18181b` | Status row, loading placeholder, copy button hover |
312
+ | `--pp-color-surface` | `#f4f4f5` | `#18181b` | Status row, placeholders, listboxes, copy hover |
227
313
  | `--pp-color-text` | `#18181b` | `#fafafa` | Text |
228
314
  | `--pp-color-text-muted` | `#52525b` | `#a1a1aa` | Labels, hints, in-progress status icon |
229
315
  | `--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 |
316
+ | `--pp-color-input` | `#8a8a93` | `#71717a` | Edges of the token and network fields |
231
317
  | `--pp-color-primary` | `#171717` | `#fafafa` | Pay button |
232
318
  | `--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 |
319
+ | `--pp-color-selected` | primary | primary | Selected tab's underline |
234
320
  | `--pp-color-focus` | `#2563eb` | `#60a5fa` | Keyboard focus ring |
235
321
  | `--pp-color-success` | `#15803d` | `#4ade80` | Credited icon |
236
322
  | `--pp-color-warning` | `#b45309` | `#fbbf24` | "Test mode" |
@@ -239,7 +325,7 @@ the theme (or map the colors) that suits its background.
239
325
  | `--pp-font-family-mono` | `ui-monospace, …` | same | Addresses, amounts to copy, hashes |
240
326
  | `--pp-font-size` | `14px` | `14px` | Root text size; other text sizes are relative to it |
241
327
  | `--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 |
328
+ | `--pp-control-height` | `44px` | `44px` | Minimum height of buttons, tabs, and fields |
243
329
 
244
330
  Import the static stylesheet once in your application entry point:
245
331
 
@@ -287,13 +373,15 @@ group by group with what their wallet shows; selecting and copying it gives the
287
373
  spaces. The QR code keeps the standard quiet zone of four modules inside 8px of white, so it scans
288
374
  on a dark page too. The status row's text stays the body color on a neutral surface; its icon
289
375
  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.
376
+ `prefers-reduced-motion: reduce`, and the selected tab and a listbox's active option keep a system
377
+ color in forced-colors mode.
292
378
 
293
379
  ### Content Security Policy
294
380
 
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
381
+ The components inject no style elements or style attributes in their markup. Serve the bundled
382
+ CSS from your own origin with `style-src 'self'`; no `unsafe-inline` style permission is needed.
383
+ An open token or network listbox is placed beside its field through the CSSOM
384
+ (`element.style.setProperty`), which that policy allows. Allow the
297
385
  payment service origin in `connect-src` for public status reads and `img-src data:` for browser
298
386
  wallet icons. Configure script and font sources for your application's own bundles.
299
387
 
@@ -340,7 +428,7 @@ holding less of the token than that sends nothing: `WalletError` with `code`
340
428
 
341
429
  ## React components
342
430
 
343
- Install `@phala/pay-react` for `Checkout`, `DepositAddress`, hooks, icons, and styles:
431
+ Install `@phala/pay-react` for `PaymentElement`, `Checkout`, `DepositAddress`, `PaymentOptionPicker`, hooks, icons, and styles:
344
432
 
345
433
  ```sh
346
434
  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.0",
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
+ }