@phala/pay 0.1.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 (69) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +202 -0
  3. package/README.md +142 -0
  4. package/dist/chains.d.ts +7 -0
  5. package/dist/chains.d.ts.map +1 -0
  6. package/dist/chains.js +16 -0
  7. package/dist/chains.js.map +1 -0
  8. package/dist/checkout.d.ts +63 -0
  9. package/dist/checkout.d.ts.map +1 -0
  10. package/dist/checkout.js +146 -0
  11. package/dist/checkout.js.map +1 -0
  12. package/dist/client.d.ts +26 -0
  13. package/dist/client.d.ts.map +1 -0
  14. package/dist/client.js +35 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/format.d.ts +13 -0
  17. package/dist/format.d.ts.map +1 -0
  18. package/dist/format.js +37 -0
  19. package/dist/format.js.map +1 -0
  20. package/dist/index.d.ts +8 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +8 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/payment.d.ts +19 -0
  25. package/dist/payment.d.ts.map +1 -0
  26. package/dist/payment.js +34 -0
  27. package/dist/payment.js.map +1 -0
  28. package/dist/quote.d.ts +36 -0
  29. package/dist/quote.d.ts.map +1 -0
  30. package/dist/quote.js +59 -0
  31. package/dist/quote.js.map +1 -0
  32. package/dist/react/Checkout.d.ts +21 -0
  33. package/dist/react/Checkout.d.ts.map +1 -0
  34. package/dist/react/Checkout.js +168 -0
  35. package/dist/react/Checkout.js.map +1 -0
  36. package/dist/react/QrCode.d.ts +8 -0
  37. package/dist/react/QrCode.d.ts.map +1 -0
  38. package/dist/react/QrCode.js +20 -0
  39. package/dist/react/QrCode.js.map +1 -0
  40. package/dist/react/appearance.d.ts +24 -0
  41. package/dist/react/appearance.d.ts.map +1 -0
  42. package/dist/react/appearance.js +95 -0
  43. package/dist/react/appearance.js.map +1 -0
  44. package/dist/react/index.d.ts +4 -0
  45. package/dist/react/index.d.ts.map +1 -0
  46. package/dist/react/index.js +4 -0
  47. package/dist/react/index.js.map +1 -0
  48. package/dist/react/useCheckout.d.ts +13 -0
  49. package/dist/react/useCheckout.d.ts.map +1 -0
  50. package/dist/react/useCheckout.js +29 -0
  51. package/dist/react/useCheckout.js.map +1 -0
  52. package/dist/wallet.d.ts +42 -0
  53. package/dist/wallet.d.ts.map +1 -0
  54. package/dist/wallet.js +135 -0
  55. package/dist/wallet.js.map +1 -0
  56. package/package.json +93 -0
  57. package/src/chains.ts +20 -0
  58. package/src/checkout.ts +225 -0
  59. package/src/client.ts +54 -0
  60. package/src/format.ts +41 -0
  61. package/src/index.ts +27 -0
  62. package/src/payment.ts +52 -0
  63. package/src/quote.ts +96 -0
  64. package/src/react/Checkout.tsx +409 -0
  65. package/src/react/QrCode.tsx +39 -0
  66. package/src/react/appearance.ts +114 -0
  67. package/src/react/index.ts +5 -0
  68. package/src/react/useCheckout.ts +48 -0
  69. package/src/wallet.ts +175 -0
@@ -0,0 +1,95 @@
1
+ const VARIABLES = {
2
+ colorPrimary: "--pp-color-primary",
3
+ colorBackground: "--pp-color-background",
4
+ colorText: "--pp-color-text",
5
+ colorTextSecondary: "--pp-color-text-secondary",
6
+ colorBorder: "--pp-color-border",
7
+ colorDanger: "--pp-color-danger",
8
+ colorSuccess: "--pp-color-success",
9
+ fontFamily: "--pp-font-family",
10
+ borderRadius: "--pp-border-radius",
11
+ };
12
+ /** The root element's custom properties for `appearance.variables`. */
13
+ export function appearanceStyle(appearance) {
14
+ const variables = appearance?.variables ?? {};
15
+ const style = {};
16
+ for (const [name, property] of Object.entries(VARIABLES)) {
17
+ const value = variables[name];
18
+ if (value !== undefined) {
19
+ style[property] = value;
20
+ }
21
+ }
22
+ return style;
23
+ }
24
+ /**
25
+ * Every rule is scoped under `.pp-root`; the variables can also be set from the page's own CSS on
26
+ * `.pp-root`.
27
+ */
28
+ export const STYLES = `
29
+ .pp-root {
30
+ --pp-color-primary: #0f62fe;
31
+ --pp-color-background: #ffffff;
32
+ --pp-color-text: #1a1a1a;
33
+ --pp-color-text-secondary: #5c5f66;
34
+ --pp-color-border: #d9dce1;
35
+ --pp-color-danger: #c62828;
36
+ --pp-color-success: #1b7f3b;
37
+ --pp-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
38
+ --pp-border-radius: 8px;
39
+ box-sizing: border-box;
40
+ max-width: 440px;
41
+ padding: 20px;
42
+ border: 1px solid var(--pp-color-border);
43
+ border-radius: var(--pp-border-radius);
44
+ background: var(--pp-color-background);
45
+ color: var(--pp-color-text);
46
+ font-family: var(--pp-font-family);
47
+ font-size: 14px;
48
+ line-height: 1.45;
49
+ }
50
+ .pp-root[data-theme="dark"] {
51
+ --pp-color-primary: #78a9ff;
52
+ --pp-color-background: #161616;
53
+ --pp-color-text: #f4f4f4;
54
+ --pp-color-text-secondary: #a8a8a8;
55
+ --pp-color-border: #393939;
56
+ --pp-color-danger: #ff8389;
57
+ --pp-color-success: #42be65;
58
+ }
59
+ .pp-root *, .pp-root *::before, .pp-root *::after { box-sizing: inherit; }
60
+ .pp-amount { margin: 0; font-size: 22px; font-weight: 600; }
61
+ .pp-subtitle { margin: 2px 0 16px; color: var(--pp-color-text-secondary); }
62
+ .pp-status { display: flex; gap: 8px; align-items: baseline; justify-content: space-between;
63
+ margin-bottom: 12px; padding: 10px 12px; border-radius: var(--pp-border-radius);
64
+ border: 1px solid var(--pp-color-border); }
65
+ .pp-status[data-tone="success"] { border-color: var(--pp-color-success); color: var(--pp-color-success); }
66
+ .pp-status[data-tone="danger"] { border-color: var(--pp-color-danger); color: var(--pp-color-danger); }
67
+ .pp-countdown { font-variant-numeric: tabular-nums; white-space: nowrap; }
68
+ .pp-notice { margin: 0 0 16px; color: var(--pp-color-text-secondary); font-size: 13px; }
69
+ .pp-notice strong { color: var(--pp-color-text); }
70
+ .pp-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--pp-color-border); margin-bottom: 16px; }
71
+ .pp-tab { appearance: none; border: 0; border-bottom: 2px solid transparent; background: none;
72
+ color: var(--pp-color-text-secondary); font: inherit; padding: 8px 10px; cursor: pointer; }
73
+ .pp-tab[aria-selected="true"] { color: var(--pp-color-text); border-bottom-color: var(--pp-color-primary); font-weight: 600; }
74
+ .pp-root button:focus-visible, .pp-root a:focus-visible { outline: 2px solid var(--pp-color-primary); outline-offset: 2px; }
75
+ .pp-wallets { display: grid; gap: 8px; }
76
+ .pp-button { display: flex; gap: 10px; align-items: center; justify-content: center; width: 100%;
77
+ appearance: none; border: 1px solid var(--pp-color-primary); border-radius: var(--pp-border-radius);
78
+ background: var(--pp-color-primary); color: #fff; font: inherit; font-weight: 600; padding: 10px 14px; cursor: pointer; }
79
+ .pp-button:disabled { opacity: 0.6; cursor: progress; }
80
+ .pp-button img { width: 20px; height: 20px; }
81
+ .pp-wallet-name { font-weight: 400; opacity: 0.85; }
82
+ .pp-message { margin: 10px 0 0; color: var(--pp-color-text-secondary); }
83
+ .pp-message[data-tone="danger"] { color: var(--pp-color-danger); }
84
+ .pp-qr-panel { display: grid; justify-items: center; gap: 10px; text-align: center; }
85
+ .pp-qr { border-radius: 4px; }
86
+ .pp-fields { display: grid; gap: 10px; margin: 0; }
87
+ .pp-field dt { color: var(--pp-color-text-secondary); font-size: 12px; }
88
+ .pp-field dd { display: flex; gap: 8px; align-items: center; margin: 2px 0 0; }
89
+ .pp-value { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
90
+ .pp-copy { flex: none; appearance: none; border: 1px solid var(--pp-color-border); border-radius: 6px;
91
+ background: none; color: var(--pp-color-text); font: inherit; font-size: 12px; padding: 3px 8px; cursor: pointer; }
92
+ .pp-tx { margin: 0 0 12px; font-size: 13px; }
93
+ .pp-tx a { color: var(--pp-color-primary); }
94
+ `;
95
+ //# sourceMappingURL=appearance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance.js","sourceRoot":"","sources":["../../src/react/appearance.ts"],"names":[],"mappings":"AAkBA,MAAM,SAAS,GAA+D;IAC5E,YAAY,EAAE,oBAAoB;IAClC,eAAe,EAAE,uBAAuB;IACxC,SAAS,EAAE,iBAAiB;IAC5B,kBAAkB,EAAE,2BAA2B;IAC/C,WAAW,EAAE,mBAAmB;IAChC,WAAW,EAAE,mBAAmB;IAChC,YAAY,EAAE,oBAAoB;IAClC,UAAU,EAAE,kBAAkB;IAC9B,YAAY,EAAE,oBAAoB;CACnC,CAAC;AAEF,uEAAuE;AACvE,MAAM,UAAU,eAAe,CAAC,UAAkC;IAChE,MAAM,SAAS,GAAG,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC;IAC9C,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,SAAS,CAAC,IAA8B,CAAC,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkErB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { Checkout, type CheckoutProps } from "./Checkout.js";
2
+ export { useCheckout, type UseCheckoutOptions, type UseCheckoutResult } from "./useCheckout.js";
3
+ export type { Appearance } from "./appearance.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAChG,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,4 @@
1
+ "use client";
2
+ export { Checkout } from "./Checkout.js";
3
+ export { useCheckout } from "./useCheckout.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAmD,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type CheckoutState } from "../checkout.js";
2
+ export interface UseCheckoutOptions {
3
+ clientSecret: string;
4
+ apiBase: string;
5
+ pollInterval?: number;
6
+ }
7
+ export interface UseCheckoutResult extends CheckoutState {
8
+ /** Reads the quote now, for example right after the wallet broadcast the payment. */
9
+ refresh: () => void;
10
+ }
11
+ /** Follows a quote's public view for as long as the component is mounted. */
12
+ export declare function useCheckout({ clientSecret, apiBase, pollInterval, }: UseCheckoutOptions): UseCheckoutResult;
13
+ //# sourceMappingURL=useCheckout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCheckout.d.ts","sourceRoot":"","sources":["../../src/react/useCheckout.ts"],"names":[],"mappings":"AACA,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE1F,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,qFAAqF;IACrF,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,EAC1B,YAAY,EACZ,OAAO,EACP,YAAY,GACb,EAAE,kBAAkB,GAAG,iBAAiB,CA0BxC"}
@@ -0,0 +1,29 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { createCheckout } from "../checkout.js";
3
+ const LOADING = { status: "loading", quote: null, error: null };
4
+ /** Follows a quote's public view for as long as the component is mounted. */
5
+ export function useCheckout({ clientSecret, apiBase, pollInterval, }) {
6
+ const [current, setCurrent] = useState({ key: "", state: LOADING });
7
+ const checkout = useRef(null);
8
+ const key = `${apiBase} ${clientSecret}`;
9
+ useEffect(() => {
10
+ const instance = createCheckout({
11
+ clientSecret,
12
+ apiBase,
13
+ ...(pollInterval === undefined ? {} : { pollInterval }),
14
+ });
15
+ checkout.current = instance;
16
+ const unsubscribe = instance.subscribe((state) => setCurrent({ key, state }));
17
+ return () => {
18
+ unsubscribe();
19
+ instance.destroy();
20
+ checkout.current = null;
21
+ };
22
+ }, [key, clientSecret, apiBase, pollInterval]);
23
+ const refresh = useCallback(() => {
24
+ void checkout.current?.refresh();
25
+ }, []);
26
+ // A new client secret starts from `loading`, not from the previous quote's state.
27
+ return { ...(current.key === key ? current.state : LOADING), refresh };
28
+ }
29
+ //# sourceMappingURL=useCheckout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../src/react/useCheckout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,cAAc,EAA4C,MAAM,gBAAgB,CAAC;AAa1F,MAAM,OAAO,GAAkB,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAE/E,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,EAC1B,YAAY,EACZ,OAAO,EACP,YAAY,GACO;IACnB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,YAAY,EAAE,CAAC;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,QAAQ,GAAG,cAAc,CAAC;YAC9B,YAAY;YACZ,OAAO;YACP,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;SACxD,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC;QAC5B,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC9E,OAAO,GAAG,EAAE;YACV,WAAW,EAAE,CAAC;YACd,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,KAAK,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,kFAAkF;IAClF,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;AACzE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type Hash } from "viem";
2
+ import type { ClientQuote } from "./quote.js";
3
+ /** EIP-6963 provider info. */
4
+ export interface WalletInfo {
5
+ uuid: string;
6
+ name: string;
7
+ /** A data URI. */
8
+ icon: string;
9
+ rdns: string;
10
+ }
11
+ /** An EIP-1193 provider; viem's `EIP1193Provider` and every injected wallet satisfy it. */
12
+ export interface EthereumProvider {
13
+ request(args: {
14
+ method: string;
15
+ params?: unknown;
16
+ }): Promise<unknown>;
17
+ }
18
+ export interface Wallet {
19
+ info: WalletInfo;
20
+ provider: EthereumProvider;
21
+ }
22
+ /** The legacy `window.ethereum` provider, offered when no wallet announces itself (EIP-6963). */
23
+ export declare const INJECTED_WALLET_UUID = "injected";
24
+ /**
25
+ * Discovers browser wallets with EIP-6963, falling back to `window.ethereum`, and calls `onChange`
26
+ * with the current list whenever a wallet announces itself. Returns the function that stops
27
+ * listening.
28
+ */
29
+ export declare function watchWallets(onChange: (wallets: Wallet[]) => void): () => void;
30
+ export type WalletErrorCode = "rejected" | "no_account" | "wrong_chain" | "failed";
31
+ export declare class WalletError extends Error {
32
+ readonly code: WalletErrorCode;
33
+ readonly name = "WalletError";
34
+ constructor(code: WalletErrorCode, message: string, options?: ErrorOptions);
35
+ }
36
+ /**
37
+ * Pays a quote from a browser wallet: connects, switches to (or adds) the quote's chain, and sends
38
+ * the ERC-20 `transfer` that the quote's `payment_uri` states. Resolves with the transaction hash
39
+ * once the wallet has broadcast it; the checkout's status follows the payment from there.
40
+ */
41
+ export declare function payWithWallet(provider: EthereumProvider, quote: ClientQuote): Promise<Hash>;
42
+ //# sourceMappingURL=wallet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,IAAI,EAEV,MAAM,MAAM,CAAC;AAGd,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2FAA2F;AAC3F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAM/C;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CA0C9E;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,QAAQ,CAAC;AAEnF,qBAAa,WAAY,SAAQ,KAAK;IAIlC,QAAQ,CAAC,IAAI,EAAE,eAAe;IAHhC,SAAkB,IAAI,iBAAiB;gBAG5B,IAAI,EAAE,eAAe,EAC9B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAIzB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BjG"}
package/dist/wallet.js ADDED
@@ -0,0 +1,135 @@
1
+ import { createWalletClient, custom, erc20Abi, } from "viem";
2
+ import { knownChain, networkName } from "./chains.js";
3
+ import { quoteTransfer } from "./payment.js";
4
+ /** The legacy `window.ethereum` provider, offered when no wallet announces itself (EIP-6963). */
5
+ export const INJECTED_WALLET_UUID = "injected";
6
+ /**
7
+ * Discovers browser wallets with EIP-6963, falling back to `window.ethereum`, and calls `onChange`
8
+ * with the current list whenever a wallet announces itself. Returns the function that stops
9
+ * listening.
10
+ */
11
+ export function watchWallets(onChange) {
12
+ if (typeof window === "undefined") {
13
+ onChange([]);
14
+ return () => undefined;
15
+ }
16
+ const announced = new Map();
17
+ const emit = () => {
18
+ const wallets = [...announced.values()];
19
+ const injected = window.ethereum;
20
+ if (wallets.length === 0 && injected !== undefined) {
21
+ wallets.push({
22
+ info: { uuid: INJECTED_WALLET_UUID, name: "Browser wallet", icon: "", rdns: "" },
23
+ provider: injected,
24
+ });
25
+ }
26
+ onChange(wallets);
27
+ };
28
+ const onAnnounce = (event) => {
29
+ const detail = event.detail;
30
+ const info = detail?.info;
31
+ if (detail?.provider === undefined ||
32
+ typeof info?.uuid !== "string" ||
33
+ typeof info.name !== "string") {
34
+ return;
35
+ }
36
+ announced.set(info.uuid, {
37
+ info: {
38
+ uuid: info.uuid,
39
+ name: info.name,
40
+ icon: typeof info.icon === "string" && info.icon.startsWith("data:image/") ? info.icon : "",
41
+ rdns: typeof info.rdns === "string" ? info.rdns : "",
42
+ },
43
+ provider: detail.provider,
44
+ });
45
+ emit();
46
+ };
47
+ window.addEventListener("eip6963:announceProvider", onAnnounce);
48
+ window.dispatchEvent(new Event("eip6963:requestProvider"));
49
+ emit();
50
+ return () => window.removeEventListener("eip6963:announceProvider", onAnnounce);
51
+ }
52
+ export class WalletError extends Error {
53
+ code;
54
+ name = "WalletError";
55
+ constructor(code, message, options) {
56
+ super(message, options);
57
+ this.code = code;
58
+ }
59
+ }
60
+ /**
61
+ * Pays a quote from a browser wallet: connects, switches to (or adds) the quote's chain, and sends
62
+ * the ERC-20 `transfer` that the quote's `payment_uri` states. Resolves with the transaction hash
63
+ * once the wallet has broadcast it; the checkout's status follows the payment from there.
64
+ */
65
+ export async function payWithWallet(provider, quote) {
66
+ const transfer = quoteTransfer(quote);
67
+ const wallet = createWalletClient({ transport: custom(provider) });
68
+ try {
69
+ const [account] = await wallet.requestAddresses();
70
+ if (account === undefined) {
71
+ throw new WalletError("no_account", "The wallet shared no account");
72
+ }
73
+ await ensureChain(wallet, transfer.chainId);
74
+ return await wallet.writeContract({
75
+ account,
76
+ chain: knownChain(transfer.chainId) ?? null,
77
+ address: transfer.token,
78
+ abi: erc20Abi,
79
+ functionName: "transfer",
80
+ args: [transfer.to, transfer.amount],
81
+ });
82
+ }
83
+ catch (error) {
84
+ if (error instanceof WalletError) {
85
+ throw error;
86
+ }
87
+ if (hasCode(error, 4001)) {
88
+ throw new WalletError("rejected", "The request was rejected in the wallet", { cause: error });
89
+ }
90
+ throw new WalletError("failed", "The wallet could not send the payment", { cause: error });
91
+ }
92
+ }
93
+ async function ensureChain(wallet, chainId) {
94
+ if ((await wallet.getChainId()) === chainId) {
95
+ return;
96
+ }
97
+ try {
98
+ await wallet.switchChain({ id: chainId });
99
+ }
100
+ catch (error) {
101
+ // 4902: the wallet does not know the chain yet.
102
+ const chain = knownChain(chainId);
103
+ if (!hasCode(error, 4902) || chain === undefined) {
104
+ throw hasCode(error, 4001)
105
+ ? error
106
+ : new WalletError("wrong_chain", `Switch your wallet to ${networkName(chainId)}`, {
107
+ cause: error,
108
+ });
109
+ }
110
+ await wallet.addChain({ chain });
111
+ if ((await wallet.getChainId()) !== chainId) {
112
+ await wallet.switchChain({ id: chainId });
113
+ }
114
+ }
115
+ if ((await wallet.getChainId()) !== chainId) {
116
+ throw new WalletError("wrong_chain", `Switch your wallet to ${networkName(chainId)}`);
117
+ }
118
+ }
119
+ /** Whether an error or any of its causes carries an EIP-1193 error code; some mobile wallets nest
120
+ * the original error under `data.originalError`. */
121
+ function hasCode(error, code) {
122
+ for (let current = error; typeof current === "object" && current !== null;) {
123
+ const value = current;
124
+ if (value.code === code) {
125
+ return true;
126
+ }
127
+ const nested = value.data?.originalError;
128
+ if (typeof nested === "object" && nested !== null && hasCode(nested, code)) {
129
+ return true;
130
+ }
131
+ current = value.cause;
132
+ }
133
+ return false;
134
+ }
135
+ //# sourceMappingURL=wallet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet.js","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@phala/pay",
3
+ "version": "0.1.0",
4
+ "description": "Phala Pay checkout: a framework-agnostic client and React bindings",
5
+ "keywords": [
6
+ "crypto",
7
+ "checkout",
8
+ "payments",
9
+ "erc20",
10
+ "eip-681",
11
+ "eip-6963",
12
+ "react",
13
+ "viem"
14
+ ],
15
+ "license": "Apache-2.0",
16
+ "homepage": "https://github.com/Phala-Network/phala-pay/tree/main/sdk/js#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/Phala-Network/phala-pay/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Phala-Network/phala-pay.git",
23
+ "directory": "sdk/js"
24
+ },
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./react": {
33
+ "types": "./dist/react/index.d.ts",
34
+ "default": "./dist/react/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "src",
40
+ "CHANGELOG.md"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "packageManager": "pnpm@12.6.0",
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
51
+ "prepare": "tsc -p tsconfig.build.json",
52
+ "typecheck": "tsc --noEmit",
53
+ "lint": "eslint .",
54
+ "test": "vitest run",
55
+ "e2e": "playwright test",
56
+ "e2e:docker": "e2e/docker.sh",
57
+ "check": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build"
58
+ },
59
+ "dependencies": {
60
+ "uqr": "^0.1.3"
61
+ },
62
+ "peerDependencies": {
63
+ "react": "^18.2.0 || ^19.0.0",
64
+ "viem": "^2.21.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "react": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/js": "^10.0.1",
73
+ "@playwright/test": "1.63.0",
74
+ "@testing-library/dom": "^10.4.2",
75
+ "@testing-library/react": "^16.3.3",
76
+ "@testing-library/user-event": "^14.6.7",
77
+ "@types/node": "^24.19.0",
78
+ "@types/react": "^18.3.31",
79
+ "@types/react-dom": "^18.3.7",
80
+ "@vitejs/plugin-react": "^6.1.1",
81
+ "eslint": "^10.11.0",
82
+ "eslint-plugin-react-hooks": "^7.1.1",
83
+ "jsdom": "^30.1.1",
84
+ "jsqr": "^1.4.0",
85
+ "react": "^18.3.1",
86
+ "react-dom": "^18.3.1",
87
+ "typescript": "~6.0.3",
88
+ "typescript-eslint": "^8.70.1",
89
+ "viem": "^2.56.9",
90
+ "vite": "^8.3.1",
91
+ "vitest": "^5.0.2"
92
+ }
93
+ }
package/src/chains.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { Chain } from "viem";
2
+ import { base, baseSepolia, mainnet, sepolia } from "viem/chains";
3
+
4
+ /** Chains a wallet can be asked to add; any other chain must already be in the wallet. */
5
+ const CHAINS: readonly Chain[] = [mainnet, sepolia, base, baseSepolia];
6
+
7
+ export function knownChain(chainId: number): Chain | undefined {
8
+ return CHAINS.find((chain) => chain.id === chainId);
9
+ }
10
+
11
+ /** A human name for the network, for example `Sepolia`. */
12
+ export function networkName(chainId: number): string {
13
+ return knownChain(chainId)?.name ?? `Chain ${chainId}`;
14
+ }
15
+
16
+ /** The block explorer link for a transaction, when the chain is known. */
17
+ export function transactionUrl(chainId: number, hash: string): string | undefined {
18
+ const explorer = knownChain(chainId)?.blockExplorers?.default.url;
19
+ return explorer === undefined ? undefined : `${explorer}/tx/${hash}`;
20
+ }