@phala/pay 0.3.0 → 0.5.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 +34 -12
- package/dist/checkout.d.ts +2 -2
- package/dist/deposit-address.d.ts +14 -1
- package/dist/deposit-address.d.ts.map +1 -1
- package/dist/deposit-address.js +18 -0
- package/dist/deposit-address.js.map +1 -1
- package/dist/format.d.ts +6 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +17 -2
- package/dist/format.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/quote.d.ts +16 -4
- package/dist/quote.d.ts.map +1 -1
- package/dist/quote.js +6 -1
- package/dist/quote.js.map +1 -1
- package/dist/react/Checkout.d.ts +10 -2
- package/dist/react/Checkout.d.ts.map +1 -1
- package/dist/react/Checkout.js +34 -10
- package/dist/react/Checkout.js.map +1 -1
- package/dist/react/DepositAddress.d.ts +2 -1
- package/dist/react/DepositAddress.d.ts.map +1 -1
- package/dist/react/DepositAddress.js +33 -8
- package/dist/react/DepositAddress.js.map +1 -1
- package/dist/wallet.d.ts +5 -3
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +26 -2
- package/dist/wallet.js.map +1 -1
- package/package.json +2 -3
- package/src/checkout.ts +2 -2
- package/src/deposit-address.ts +36 -1
- package/src/format.ts +20 -2
- package/src/index.ts +1 -0
- package/src/quote.ts +22 -5
- package/src/react/Checkout.tsx +53 -6
- package/src/react/DepositAddress.tsx +41 -12
- package/src/wallet.ts +36 -3
- package/CHANGELOG.md +0 -160
|
@@ -5,9 +5,11 @@ import { formatUnits } from "viem";
|
|
|
5
5
|
import { networkName } from "../chains.js";
|
|
6
6
|
import {
|
|
7
7
|
retrieveDepositAddress,
|
|
8
|
+
type ClientDepositAddress,
|
|
8
9
|
type DepositAddressPayment,
|
|
9
10
|
} from "../deposit-address.js";
|
|
10
11
|
import { CheckoutError, pollDelay } from "../checkout.js";
|
|
12
|
+
import { formatWait } from "../format.js";
|
|
11
13
|
import { depositAddressTransfer, type DepositAddressDetails } from "../payment.js";
|
|
12
14
|
import { STYLES, appearanceStyle, type Appearance } from "./appearance.js";
|
|
13
15
|
import { Field } from "./Field.js";
|
|
@@ -23,7 +25,8 @@ export interface DepositAddressProps {
|
|
|
23
25
|
/**
|
|
24
26
|
* The `client_secret` of the same response. With `apiBase`, the component follows the
|
|
25
27
|
* address's payments and shows each one within about a block of arriving: received with its
|
|
26
|
-
* confirmations, then credited
|
|
28
|
+
* confirmations, then credited, and states each network's typical credit time. Display only;
|
|
29
|
+
* credit from your `deposit.credited` webhook.
|
|
27
30
|
*/
|
|
28
31
|
clientSecret?: string;
|
|
29
32
|
/** The service origin, for example `https://pay.example.com`; needed with `clientSecret`. */
|
|
@@ -52,7 +55,8 @@ export function DepositAddress({
|
|
|
52
55
|
className,
|
|
53
56
|
}: DepositAddressProps) {
|
|
54
57
|
const { networks } = depositAddress;
|
|
55
|
-
const
|
|
58
|
+
const view = useClientView(clientSecret, apiBase, pollInterval ?? 3000);
|
|
59
|
+
const payments = view?.payments ?? [];
|
|
56
60
|
const [selectedChain, setSelectedChain] = useState(chainId ?? networks[0]?.chain_id);
|
|
57
61
|
const [selectedAsset, setSelectedAsset] = useState(asset);
|
|
58
62
|
const network = networks.find((candidate) => candidate.chain_id === selectedChain) ?? networks[0];
|
|
@@ -135,23 +139,23 @@ export function DepositAddress({
|
|
|
135
139
|
)}
|
|
136
140
|
<p className="pp-message">
|
|
137
141
|
Send only {tokens.join(", ")} on {networks.map((each) => networkName(each.chain_id)).join(", ")}
|
|
138
|
-
.
|
|
139
|
-
|
|
142
|
+
. {creditMessage(networks.map((each) => each.chain_id), view)} Other tokens and networks are not
|
|
143
|
+
credited. You can reuse this address.
|
|
140
144
|
</p>
|
|
141
145
|
</div>
|
|
142
146
|
);
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
/** The address's
|
|
146
|
-
function
|
|
149
|
+
/** The address's public view, read every `interval` while mounted; `null` without a secret. */
|
|
150
|
+
function useClientView(
|
|
147
151
|
clientSecret: string | undefined,
|
|
148
152
|
apiBase: string | undefined,
|
|
149
153
|
interval: number,
|
|
150
|
-
):
|
|
154
|
+
): ClientDepositAddress | null {
|
|
151
155
|
const key = clientSecret === undefined || apiBase === undefined ? "" : `${apiBase} ${clientSecret}`;
|
|
152
|
-
const [current, setCurrent] = useState<{ key: string;
|
|
156
|
+
const [current, setCurrent] = useState<{ key: string; view: ClientDepositAddress | null }>({
|
|
153
157
|
key: "",
|
|
154
|
-
|
|
158
|
+
view: null,
|
|
155
159
|
});
|
|
156
160
|
useEffect(() => {
|
|
157
161
|
if (clientSecret === undefined || apiBase === undefined) {
|
|
@@ -166,7 +170,7 @@ function usePayments(
|
|
|
166
170
|
const view = await retrieveDepositAddress({ clientSecret, apiBase });
|
|
167
171
|
failures = 0;
|
|
168
172
|
if (!stopped) {
|
|
169
|
-
setCurrent({ key,
|
|
173
|
+
setCurrent({ key, view });
|
|
170
174
|
}
|
|
171
175
|
} catch (error) {
|
|
172
176
|
failures += 1;
|
|
@@ -186,8 +190,33 @@ function usePayments(
|
|
|
186
190
|
clearTimeout(timer);
|
|
187
191
|
};
|
|
188
192
|
}, [key, clientSecret, apiBase, interval]);
|
|
189
|
-
// Another address starts without the previous one's
|
|
190
|
-
return current.key === key && key !== "" ? current.
|
|
193
|
+
// Another address starts without the previous one's view.
|
|
194
|
+
return current.key === key && key !== "" ? current.view : null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* When a payment on each of `chainIds` is credited, from `view`'s typical credit times, for
|
|
199
|
+
* example "usually in about 30 seconds on Ethereum and about 7 seconds on Base". Before the view
|
|
200
|
+
* is loaded it names no time.
|
|
201
|
+
*/
|
|
202
|
+
function creditMessage(chainIds: number[], view: ClientDepositAddress | null): string {
|
|
203
|
+
const names = new Map<number, string[]>();
|
|
204
|
+
for (const network of view?.networks ?? []) {
|
|
205
|
+
if (chainIds.includes(network.chain_id)) {
|
|
206
|
+
const seconds = network.typical_credit_seconds;
|
|
207
|
+
names.set(seconds, [...(names.get(seconds) ?? []), networkName(network.chain_id)]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (names.size === 0) {
|
|
211
|
+
return "Any amount is credited at the market rate once it is confirmed on its network.";
|
|
212
|
+
}
|
|
213
|
+
const list = new Intl.ListFormat("en", { type: "conjunction" });
|
|
214
|
+
const [only, ...others] = names.keys();
|
|
215
|
+
const usually =
|
|
216
|
+
only !== undefined && others.length === 0
|
|
217
|
+
? formatWait(only)
|
|
218
|
+
: list.format([...names].map(([seconds, on]) => `${formatWait(seconds)} on ${list.format(on)}`));
|
|
219
|
+
return `Any amount is credited at the market rate when it arrives, usually in ${usually}.`;
|
|
191
220
|
}
|
|
192
221
|
|
|
193
222
|
function paymentMessage(payment: DepositAddressPayment): string {
|
package/src/wallet.ts
CHANGED
|
@@ -6,10 +6,13 @@ import {
|
|
|
6
6
|
erc20Abi,
|
|
7
7
|
SwitchChainError,
|
|
8
8
|
UserRejectedRequestError,
|
|
9
|
+
type Address,
|
|
9
10
|
type Hash,
|
|
10
11
|
type WalletClient,
|
|
11
12
|
} from "viem";
|
|
13
|
+
import { readContract } from "viem/actions";
|
|
12
14
|
import { knownChain, networkName } from "./chains.js";
|
|
15
|
+
import { formatTokenAmount, formatUnitsGrouped } from "./format.js";
|
|
13
16
|
import { quoteTransfer } from "./payment.js";
|
|
14
17
|
import type { ClientQuote } from "./quote.js";
|
|
15
18
|
|
|
@@ -83,7 +86,12 @@ function isProvider(value: unknown): value is EthereumProvider {
|
|
|
83
86
|
return typeof (value as Partial<EthereumProvider> | null | undefined)?.request === "function";
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
export type WalletErrorCode =
|
|
89
|
+
export type WalletErrorCode =
|
|
90
|
+
| "rejected"
|
|
91
|
+
| "no_account"
|
|
92
|
+
| "wrong_chain"
|
|
93
|
+
| "insufficient_balance"
|
|
94
|
+
| "failed";
|
|
87
95
|
|
|
88
96
|
export class WalletError extends Error {
|
|
89
97
|
override readonly name = "WalletError";
|
|
@@ -101,8 +109,10 @@ export class WalletError extends Error {
|
|
|
101
109
|
* Pays a quote from a wallet: a viem `WalletClient` (for example wagmi's `useWalletClient()`), or an
|
|
102
110
|
* EIP-1193 provider such as a discovered `Wallet`'s. Connects (unless the client already has an
|
|
103
111
|
* account), switches to (or adds) the quote's chain, and sends the ERC-20 `transfer` that the quote's
|
|
104
|
-
* `payment_uri` states.
|
|
105
|
-
*
|
|
112
|
+
* `payment_uri` states. Before sending, it reads the account's token balance and, when it is below
|
|
113
|
+
* the quote's amount, sends nothing and throws `WalletError` `insufficient_balance`: the transfer
|
|
114
|
+
* would revert and still cost gas. Resolves with the transaction hash once the wallet has broadcast
|
|
115
|
+
* it; the checkout's status follows the payment from there.
|
|
106
116
|
*/
|
|
107
117
|
export async function payWithWallet(
|
|
108
118
|
wallet: WalletClient | EthereumProvider,
|
|
@@ -116,6 +126,16 @@ export async function payWithWallet(
|
|
|
116
126
|
throw new WalletError("no_account", "The wallet shared no account");
|
|
117
127
|
}
|
|
118
128
|
await ensureChain(client, transfer.chainId);
|
|
129
|
+
const address = typeof account === "string" ? account : account.address;
|
|
130
|
+
const balance = await tokenBalance(client, transfer.token, address);
|
|
131
|
+
if (balance !== undefined && balance < transfer.amount) {
|
|
132
|
+
const symbol = quote.asset.toUpperCase();
|
|
133
|
+
throw new WalletError(
|
|
134
|
+
"insufficient_balance",
|
|
135
|
+
`Your wallet holds ${formatUnitsGrouped(balance, quote.decimals)} ${symbol}, less than the ` +
|
|
136
|
+
`${formatTokenAmount(quote)} ${symbol} to pay. Nothing was sent.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
119
139
|
return await client.writeContract({
|
|
120
140
|
account,
|
|
121
141
|
chain: knownChain(transfer.chainId) ?? null,
|
|
@@ -135,6 +155,19 @@ export async function payWithWallet(
|
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The account's balance of `token`, read through the wallet on its current chain; `undefined` when
|
|
160
|
+
* the wallet cannot read it. The check only spares the payer a reverting transfer, so a wallet that
|
|
161
|
+
* does not serve `eth_call` still pays.
|
|
162
|
+
*/
|
|
163
|
+
async function tokenBalance(wallet: WalletClient, token: Address, owner: Address): Promise<bigint | undefined> {
|
|
164
|
+
try {
|
|
165
|
+
return await readContract(wallet, { address: token, abi: erc20Abi, functionName: "balanceOf", args: [owner] });
|
|
166
|
+
} catch {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
138
171
|
function isWalletClient(wallet: WalletClient | EthereumProvider): wallet is WalletClient {
|
|
139
172
|
return "writeContract" in wallet;
|
|
140
173
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@phala/pay` are recorded here. The format follows
|
|
4
|
-
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
|
-
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
-
|
|
7
|
-
## [Unreleased]
|
|
8
|
-
|
|
9
|
-
## [0.3.0] - 2026-10-01
|
|
10
|
-
|
|
11
|
-
### Changed (breaking)
|
|
12
|
-
|
|
13
|
-
- `constructEvent` takes webhook public keys only in Standard Webhooks' `whpk_` form (`whpk_` and
|
|
14
|
-
the standard base64 of the key), as `GET /v1/attestation` lists them; a hex or bare base64 key
|
|
15
|
-
is refused.
|
|
16
|
-
- `parseClientQuote` requires `confirmations`, an integer or `null`, as the service always sends
|
|
17
|
-
it.
|
|
18
|
-
- `constructEvent` requires the event's `actor` and `request` (`null` or the causing request), as
|
|
19
|
-
the service always sends them, and refuses an envelope without them; `WebhookEvent.actor` and
|
|
20
|
-
`request` are no longer optional.
|
|
21
|
-
|
|
22
|
-
### Fixed
|
|
23
|
-
|
|
24
|
-
- `constructEvent` returns the event's `request` (the causing API request's `id` and
|
|
25
|
-
`idempotency_key`, or `null`), as `WebhookEvent` declares; it was dropped. A malformed `request`
|
|
26
|
-
is refused like any malformed event.
|
|
27
|
-
|
|
28
|
-
## [0.2.0] - 2026-09-29
|
|
29
|
-
|
|
30
|
-
### Changed (breaking)
|
|
31
|
-
|
|
32
|
-
- `quoteAddress(forwarder, quote, treasury, account)` takes the treasury explicitly, like
|
|
33
|
-
`depositAddress`; it no longer reads the quote's own `treasury`, which a compromised service
|
|
34
|
-
could set to an attacker's with that treasury's valid address.
|
|
35
|
-
|
|
36
|
-
- `quoteSalt(account, clientReferenceId, quoteId)` replaces `lockSalt`: a quote's address salt is
|
|
37
|
-
tagged `"quote"` (design D3; it was `"lock"`), so `quoteAddress` recomputes the service's
|
|
38
|
-
addresses again.
|
|
39
|
-
|
|
40
|
-
- `<Checkout expectedAddress>` is required, as are `expectedAddress` in `createCheckout`,
|
|
41
|
-
`useCheckout`, `PhalaPay.checkout(clientSecret, { expectedAddress })`, and the second argument of
|
|
42
|
-
`retrieveQuote`/`PhalaPay.retrieveQuote`: the address your backend recomputed. A quote naming
|
|
43
|
-
another address fails closed with `CheckoutError` `address_mismatch`; the component shows
|
|
44
|
-
nothing to pay.
|
|
45
|
-
- `ClientQuote` has `livemode` (the checkout shows "Test mode") and `payment_status: "reversed"`;
|
|
46
|
-
`CheckoutStatus` gains `reversed`, with "reversed" copy.
|
|
47
|
-
|
|
48
|
-
### Added
|
|
49
|
-
|
|
50
|
-
- `verifyQuoteAddress(pins, quote)` and `verifyDepositAddress(pins, address)` in
|
|
51
|
-
`@phala/pay/server` recompute an address from `AddressPins` you configure (`account`, `factory`,
|
|
52
|
-
`implementation`, `treasuries` per chain), never from the response's treasury, and throw
|
|
53
|
-
`AddressMismatchError`; in live mode a chain without a pinned treasury fails closed, and test mode
|
|
54
|
-
falls back to the response's treasury with a console warning.
|
|
55
|
-
|
|
56
|
-
- `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
|
|
57
|
-
`retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
|
|
58
|
-
before reading again.
|
|
59
|
-
- `WebhookEvent.request` (the request that caused the event, or `null` for the service's
|
|
60
|
-
workers) and the documented `data.previous_attributes` of `*.updated` events.
|
|
61
|
-
|
|
62
|
-
- `<DepositAddress clientSecret apiBase pollInterval?>` follows the address's payments and shows
|
|
63
|
-
each within about a block of arriving ("1.5 PHA received on Sepolia, 1 confirmation"), then
|
|
64
|
-
credited, rejected, or reversed; `retrieveDepositAddress`, `parseClientDepositAddress`, and
|
|
65
|
-
`depositAddressIdFromClientSecret` read the same public view.
|
|
66
|
-
- `@phala/pay/server`, which takes no secret key: `constructEvent(payload, headers, publicKeys,
|
|
67
|
-
{ expectedAccount, expectedLivemode })` verifies Standard Webhooks `v1a` deliveries with
|
|
68
|
-
WebCrypto (Node 20+, Deno, Bun, edge runtimes), failing closed for another account or mode;
|
|
69
|
-
`forwarderAddress`, `quoteSalt`, `depositAddressSalt`, `quoteAddress`, and `depositAddress`
|
|
70
|
-
recompute addresses; `flushTransaction`, `flushTransactions`, `safeBatch`, and `batchChecksum`
|
|
71
|
-
build sweeps offline, with the Transaction Builder's `BatchFile` type.
|
|
72
|
-
|
|
73
|
-
- `<DepositAddress depositAddress chainId? asset?>` (`@phala/pay/react`): a customer's persistent
|
|
74
|
-
deposit address, one address for all supported tokens and networks. The payer picks a network
|
|
75
|
-
and a token; it shows a QR code of that token's EIP-681 request, the token contract and address
|
|
76
|
-
to copy (each network's own address where they differ), and "send only supported tokens; any
|
|
77
|
-
amount, credited at the market rate when it arrives" copy. `depositAddressTransfer(network,
|
|
78
|
-
asset)` reads and checks one token's amount-less `payment_uri`, and `parseTransferUri(uri)` is
|
|
79
|
-
the EIP-681 ERC-20 transfer parser `quoteTransfer` uses.
|
|
80
|
-
|
|
81
|
-
- `<Checkout walletClient>` and `payWithWallet(walletClient, quote)`: pay with the page's own viem
|
|
82
|
-
`WalletClient`, such as wagmi's `useWalletClient()` (RainbowKit, ConnectKit, AppKit). The wallet
|
|
83
|
-
tab then uses its account and switches its chain, without listing the browser's wallets.
|
|
84
|
-
|
|
85
|
-
### Changed
|
|
86
|
-
|
|
87
|
-
- `<Checkout>` copy for fast credit: a seen payment reads "Payment received, N confirmations.
|
|
88
|
-
Crediting in about 30 seconds"; `confirming` and `credited` are documented for crediting at the
|
|
89
|
-
route's confirmation (two blocks on Ethereum) instead of finality.
|
|
90
|
-
- `watchWallets` discovers EIP-6963 wallets through [mipd](https://github.com/wevm/mipd), wagmi's
|
|
91
|
-
discovery store, now a dependency; the `window.ethereum` fallback stays. Wallet errors are
|
|
92
|
-
classified with viem's `UserRejectedRequestError` and `SwitchChainError`.
|
|
93
|
-
|
|
94
|
-
## [0.1.2] - 2026-09-27
|
|
95
|
-
|
|
96
|
-
### Added
|
|
97
|
-
|
|
98
|
-
- `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
|
|
99
|
-
`retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
|
|
100
|
-
before reading again.
|
|
101
|
-
- `WebhookEvent.request` (the request that caused the event, or `null` for the service's
|
|
102
|
-
workers) and the documented `data.previous_attributes` of `*.updated` events.
|
|
103
|
-
|
|
104
|
-
- `<Checkout onChange>`: called once per status change with `{ status, quote, error }`, like
|
|
105
|
-
Stripe Elements' `onChange`, so the page can react to `seen` or `confirming` without its own
|
|
106
|
-
polling.
|
|
107
|
-
|
|
108
|
-
### Fixed
|
|
109
|
-
|
|
110
|
-
- The transaction link in `<Checkout>` uses the text color with an underline instead of
|
|
111
|
-
`colorPrimary`, so it stays readable with a light brand color.
|
|
112
|
-
|
|
113
|
-
## [0.1.1] - 2026-09-27
|
|
114
|
-
|
|
115
|
-
### Added
|
|
116
|
-
|
|
117
|
-
- `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
|
|
118
|
-
`retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
|
|
119
|
-
before reading again.
|
|
120
|
-
- `WebhookEvent.request` (the request that caused the event, or `null` for the service's
|
|
121
|
-
workers) and the documented `data.previous_attributes` of `*.updated` events.
|
|
122
|
-
|
|
123
|
-
- `appearance.variables.accessibleColorOnColorPrimary` (`--pp-accessible-color-on-color-primary`,
|
|
124
|
-
default `#ffffff`): the color of text on a `colorPrimary` background, such as the pay button's
|
|
125
|
-
label, so a light brand primary stays readable. Same name and meaning as Stripe's current
|
|
126
|
-
Appearance API variable.
|
|
127
|
-
|
|
128
|
-
### Fixed
|
|
129
|
-
|
|
130
|
-
- `<Checkout>` shows the full transaction hash after a wallet payment, wrapping in monospace,
|
|
131
|
-
instead of truncating it, so a payer can verify every character; a long token amount wraps
|
|
132
|
-
instead of overflowing.
|
|
133
|
-
|
|
134
|
-
## [0.1.0] - 2026-09-27
|
|
135
|
-
|
|
136
|
-
### Added
|
|
137
|
-
|
|
138
|
-
- `CheckoutError.requestId` (the failed response's `Request-Id`) and, for `rate_limited`,
|
|
139
|
-
`retryAfter` (its `Retry-After`, in seconds), which `createCheckout` and `<DepositAddress>` wait
|
|
140
|
-
before reading again.
|
|
141
|
-
- `WebhookEvent.request` (the request that caused the event, or `null` for the service's
|
|
142
|
-
workers) and the documented `data.previous_attributes` of `*.updated` events.
|
|
143
|
-
|
|
144
|
-
- `PhalaPay({ apiBase })`: `retrieveQuote(clientSecret)` reads a quote's public view
|
|
145
|
-
(`GET /v1/quotes/{id}?client_secret=…`) and `checkout(clientSecret)` follows it, exposing the
|
|
146
|
-
payer-facing status (also `createCheckout` and `retrieveQuote`).
|
|
147
|
-
- `watchWallets` (EIP-6963, `window.ethereum` fallback) and `payWithWallet`: pay a quote's EIP-681
|
|
148
|
-
ERC-20 transfer from a browser wallet, switching or adding the chain.
|
|
149
|
-
- `@phala/pay/react`: `<Checkout>` with wallet ("Pay with crypto", `buttonText`), QR code, and
|
|
150
|
-
manual payment, live status, and Stripe-style `appearance`; `useCheckout`.
|
|
151
|
-
- `formatTokenAmount(quote, locale?)` shows the exact token amount grouped for the locale and
|
|
152
|
-
without trailing zeros (`1,273.9185`); `tokenAmount(quote)` is the plain decimal a wallet
|
|
153
|
-
accepts, which `<Checkout>`'s "Exact amount" copies.
|
|
154
|
-
|
|
155
|
-
[Unreleased]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.3.0...HEAD
|
|
156
|
-
[0.3.0]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.2.0...sdk-js-v0.3.0
|
|
157
|
-
[0.2.0]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.2...sdk-js-v0.2.0
|
|
158
|
-
[0.1.2]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.1...sdk-js-v0.1.2
|
|
159
|
-
[0.1.1]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.0...sdk-js-v0.1.1
|
|
160
|
-
[0.1.0]: https://github.com/Phala-Network/phala-pay/releases/tag/sdk-js-v0.1.0
|