@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DepositAddress.d.ts","sourceRoot":"","sources":["../../src/react/DepositAddress.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DepositAddress.d.ts","sourceRoot":"","sources":["../../src/react/DepositAddress.tsx"],"names":[],"mappings":"AAYA,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI3E,MAAM,WAAW,mBAAmB;IAClC,iFAAiF;IACjF,cAAc,EAAE,qBAAqB,CAAC;IACtC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,cAAc,EACd,OAAO,EACP,KAAK,EACL,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,UAAU,EACV,SAAS,GACV,EAAE,mBAAmB,+BA2FrB"}
|
|
@@ -5,6 +5,7 @@ import { formatUnits } from "viem";
|
|
|
5
5
|
import { networkName } from "../chains.js";
|
|
6
6
|
import { retrieveDepositAddress, } from "../deposit-address.js";
|
|
7
7
|
import { CheckoutError, pollDelay } from "../checkout.js";
|
|
8
|
+
import { formatWait } from "../format.js";
|
|
8
9
|
import { depositAddressTransfer } from "../payment.js";
|
|
9
10
|
import { STYLES, appearanceStyle } from "./appearance.js";
|
|
10
11
|
import { Field } from "./Field.js";
|
|
@@ -18,7 +19,8 @@ import { QrCode } from "./QrCode.js";
|
|
|
18
19
|
*/
|
|
19
20
|
export function DepositAddress({ depositAddress, chainId, asset, clientSecret, apiBase, pollInterval, appearance, className, }) {
|
|
20
21
|
const { networks } = depositAddress;
|
|
21
|
-
const
|
|
22
|
+
const view = useClientView(clientSecret, apiBase, pollInterval ?? 3000);
|
|
23
|
+
const payments = view?.payments ?? [];
|
|
22
24
|
const [selectedChain, setSelectedChain] = useState(chainId ?? networks[0]?.chain_id);
|
|
23
25
|
const [selectedAsset, setSelectedAsset] = useState(asset);
|
|
24
26
|
const network = networks.find((candidate) => candidate.chain_id === selectedChain) ?? networks[0];
|
|
@@ -39,14 +41,14 @@ export function DepositAddress({ depositAddress, chainId, asset, clientSecret, a
|
|
|
39
41
|
setSelectedChain(each.chain_id);
|
|
40
42
|
}, children: networkName(each.chain_id) }, each.chain_id))) })), network.assets.length > 1 && (_jsx("div", { className: "pp-tabs", role: "tablist", "aria-label": "Token", children: network.assets.map((each) => (_jsx("button", { type: "button", role: "tab", className: "pp-tab", "aria-selected": each.asset === token.asset, onClick: () => {
|
|
41
43
|
setSelectedAsset(each.asset);
|
|
42
|
-
}, children: each.asset.toUpperCase() }, each.asset))) })), _jsx("div", { className: "pp-qr-panel", children: _jsx(QrCode, { value: token.payment_uri, label: `Deposit address for ${symbol} on ${name}` }) }), _jsxs("dl", { className: "pp-fields", children: [_jsx(Field, { label: "Network", value: `${name} (chain ID ${network.chain_id})` }), _jsx(Field, { label: `Token (${symbol}) contract`, value: contract, copy: true }), _jsx(Field, { label: "Deposit address", value: to, copy: true })] }), payments.length > 0 && (_jsx("ul", { className: "pp-payments", "aria-live": "polite", "aria-label": "Payments", children: payments.map((payment) => (_jsx("li", { children: paymentMessage(payment) }, `${payment.chain_id}:${payment.tx_hash}:${payment.created}`))) })), _jsxs("p", { className: "pp-message", children: ["Send only ", tokens.join(", "), " on ", networks.map((each) => networkName(each.chain_id)).join(", "), ".
|
|
44
|
+
}, children: each.asset.toUpperCase() }, each.asset))) })), _jsx("div", { className: "pp-qr-panel", children: _jsx(QrCode, { value: token.payment_uri, label: `Deposit address for ${symbol} on ${name}` }) }), _jsxs("dl", { className: "pp-fields", children: [_jsx(Field, { label: "Network", value: `${name} (chain ID ${network.chain_id})` }), _jsx(Field, { label: `Token (${symbol}) contract`, value: contract, copy: true }), _jsx(Field, { label: "Deposit address", value: to, copy: true })] }), payments.length > 0 && (_jsx("ul", { className: "pp-payments", "aria-live": "polite", "aria-label": "Payments", children: payments.map((payment) => (_jsx("li", { children: paymentMessage(payment) }, `${payment.chain_id}:${payment.tx_hash}:${payment.created}`))) })), _jsxs("p", { className: "pp-message", children: ["Send only ", tokens.join(", "), " on ", networks.map((each) => networkName(each.chain_id)).join(", "), ". ", creditMessage(networks.map((each) => each.chain_id), view), " Other tokens and networks are not credited. You can reuse this address."] })] }));
|
|
43
45
|
}
|
|
44
|
-
/** The address's
|
|
45
|
-
function
|
|
46
|
+
/** The address's public view, read every `interval` while mounted; `null` without a secret. */
|
|
47
|
+
function useClientView(clientSecret, apiBase, interval) {
|
|
46
48
|
const key = clientSecret === undefined || apiBase === undefined ? "" : `${apiBase} ${clientSecret}`;
|
|
47
49
|
const [current, setCurrent] = useState({
|
|
48
50
|
key: "",
|
|
49
|
-
|
|
51
|
+
view: null,
|
|
50
52
|
});
|
|
51
53
|
useEffect(() => {
|
|
52
54
|
if (clientSecret === undefined || apiBase === undefined) {
|
|
@@ -61,7 +63,7 @@ function usePayments(clientSecret, apiBase, interval) {
|
|
|
61
63
|
const view = await retrieveDepositAddress({ clientSecret, apiBase });
|
|
62
64
|
failures = 0;
|
|
63
65
|
if (!stopped) {
|
|
64
|
-
setCurrent({ key,
|
|
66
|
+
setCurrent({ key, view });
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
catch (error) {
|
|
@@ -82,8 +84,31 @@ function usePayments(clientSecret, apiBase, interval) {
|
|
|
82
84
|
clearTimeout(timer);
|
|
83
85
|
};
|
|
84
86
|
}, [key, clientSecret, apiBase, interval]);
|
|
85
|
-
// Another address starts without the previous one's
|
|
86
|
-
return current.key === key && key !== "" ? current.
|
|
87
|
+
// Another address starts without the previous one's view.
|
|
88
|
+
return current.key === key && key !== "" ? current.view : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* When a payment on each of `chainIds` is credited, from `view`'s typical credit times, for
|
|
92
|
+
* example "usually in about 30 seconds on Ethereum and about 7 seconds on Base". Before the view
|
|
93
|
+
* is loaded it names no time.
|
|
94
|
+
*/
|
|
95
|
+
function creditMessage(chainIds, view) {
|
|
96
|
+
const names = new Map();
|
|
97
|
+
for (const network of view?.networks ?? []) {
|
|
98
|
+
if (chainIds.includes(network.chain_id)) {
|
|
99
|
+
const seconds = network.typical_credit_seconds;
|
|
100
|
+
names.set(seconds, [...(names.get(seconds) ?? []), networkName(network.chain_id)]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (names.size === 0) {
|
|
104
|
+
return "Any amount is credited at the market rate once it is confirmed on its network.";
|
|
105
|
+
}
|
|
106
|
+
const list = new Intl.ListFormat("en", { type: "conjunction" });
|
|
107
|
+
const [only, ...others] = names.keys();
|
|
108
|
+
const usually = only !== undefined && others.length === 0
|
|
109
|
+
? formatWait(only)
|
|
110
|
+
: list.format([...names].map(([seconds, on]) => `${formatWait(seconds)} on ${list.format(on)}`));
|
|
111
|
+
return `Any amount is credited at the market rate when it arrives, usually in ${usually}.`;
|
|
87
112
|
}
|
|
88
113
|
function paymentMessage(payment) {
|
|
89
114
|
const amount = payment.asset === null || payment.decimals === null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DepositAddress.js","sourceRoot":"","sources":["../../src/react/DepositAddress.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACL,sBAAsB,
|
|
1
|
+
{"version":3,"file":"DepositAddress.js","sourceRoot":"","sources":["../../src/react/DepositAddress.tsx"],"names":[],"mappings":"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"}
|
package/dist/wallet.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare const INJECTED_WALLET_UUID = "injected";
|
|
|
27
27
|
* itself. Returns the function that stops listening.
|
|
28
28
|
*/
|
|
29
29
|
export declare function watchWallets(onChange: (wallets: Wallet[]) => void): () => void;
|
|
30
|
-
export type WalletErrorCode = "rejected" | "no_account" | "wrong_chain" | "failed";
|
|
30
|
+
export type WalletErrorCode = "rejected" | "no_account" | "wrong_chain" | "insufficient_balance" | "failed";
|
|
31
31
|
export declare class WalletError extends Error {
|
|
32
32
|
readonly code: WalletErrorCode;
|
|
33
33
|
readonly name = "WalletError";
|
|
@@ -37,8 +37,10 @@ export declare class WalletError extends Error {
|
|
|
37
37
|
* Pays a quote from a wallet: a viem `WalletClient` (for example wagmi's `useWalletClient()`), or an
|
|
38
38
|
* EIP-1193 provider such as a discovered `Wallet`'s. Connects (unless the client already has an
|
|
39
39
|
* account), switches to (or adds) the quote's chain, and sends the ERC-20 `transfer` that the quote's
|
|
40
|
-
* `payment_uri` states.
|
|
41
|
-
*
|
|
40
|
+
* `payment_uri` states. Before sending, it reads the account's token balance and, when it is below
|
|
41
|
+
* the quote's amount, sends nothing and throws `WalletError` `insufficient_balance`: the transfer
|
|
42
|
+
* would revert and still cost gas. Resolves with the transaction hash once the wallet has broadcast
|
|
43
|
+
* it; the checkout's status follows the payment from there.
|
|
42
44
|
*/
|
|
43
45
|
export declare function payWithWallet(wallet: WalletClient | EthereumProvider, quote: ClientQuote): Promise<Hash>;
|
|
44
46
|
//# sourceMappingURL=wallet.d.ts.map
|
package/dist/wallet.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,IAAI,EACT,KAAK,YAAY,EAClB,MAAM,MAAM,CAAC;AAKd,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;AAE/C;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAa9E;AA8BD,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,YAAY,GACZ,aAAa,GACb,sBAAsB,GACtB,QAAQ,CAAC;AAEb,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;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,GAAG,gBAAgB,EACvC,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAoCf"}
|
package/dist/wallet.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createStore } from "mipd";
|
|
2
2
|
import { BaseError, createWalletClient, custom, erc20Abi, SwitchChainError, UserRejectedRequestError, } from "viem";
|
|
3
|
+
import { readContract } from "viem/actions";
|
|
3
4
|
import { knownChain, networkName } from "./chains.js";
|
|
5
|
+
import { formatTokenAmount, formatUnitsGrouped } from "./format.js";
|
|
4
6
|
import { quoteTransfer } from "./payment.js";
|
|
5
7
|
/** The legacy `window.ethereum` provider, offered when no wallet announces itself (EIP-6963). */
|
|
6
8
|
export const INJECTED_WALLET_UUID = "injected";
|
|
@@ -59,8 +61,10 @@ export class WalletError extends Error {
|
|
|
59
61
|
* Pays a quote from a wallet: a viem `WalletClient` (for example wagmi's `useWalletClient()`), or an
|
|
60
62
|
* EIP-1193 provider such as a discovered `Wallet`'s. Connects (unless the client already has an
|
|
61
63
|
* account), switches to (or adds) the quote's chain, and sends the ERC-20 `transfer` that the quote's
|
|
62
|
-
* `payment_uri` states.
|
|
63
|
-
*
|
|
64
|
+
* `payment_uri` states. Before sending, it reads the account's token balance and, when it is below
|
|
65
|
+
* the quote's amount, sends nothing and throws `WalletError` `insufficient_balance`: the transfer
|
|
66
|
+
* would revert and still cost gas. Resolves with the transaction hash once the wallet has broadcast
|
|
67
|
+
* it; the checkout's status follows the payment from there.
|
|
64
68
|
*/
|
|
65
69
|
export async function payWithWallet(wallet, quote) {
|
|
66
70
|
const transfer = quoteTransfer(quote);
|
|
@@ -71,6 +75,13 @@ export async function payWithWallet(wallet, quote) {
|
|
|
71
75
|
throw new WalletError("no_account", "The wallet shared no account");
|
|
72
76
|
}
|
|
73
77
|
await ensureChain(client, transfer.chainId);
|
|
78
|
+
const address = typeof account === "string" ? account : account.address;
|
|
79
|
+
const balance = await tokenBalance(client, transfer.token, address);
|
|
80
|
+
if (balance !== undefined && balance < transfer.amount) {
|
|
81
|
+
const symbol = quote.asset.toUpperCase();
|
|
82
|
+
throw new WalletError("insufficient_balance", `Your wallet holds ${formatUnitsGrouped(balance, quote.decimals)} ${symbol}, less than the ` +
|
|
83
|
+
`${formatTokenAmount(quote)} ${symbol} to pay. Nothing was sent.`);
|
|
84
|
+
}
|
|
74
85
|
return await client.writeContract({
|
|
75
86
|
account,
|
|
76
87
|
chain: knownChain(transfer.chainId) ?? null,
|
|
@@ -90,6 +101,19 @@ export async function payWithWallet(wallet, quote) {
|
|
|
90
101
|
throw new WalletError("failed", "The wallet could not send the payment", { cause: error });
|
|
91
102
|
}
|
|
92
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The account's balance of `token`, read through the wallet on its current chain; `undefined` when
|
|
106
|
+
* the wallet cannot read it. The check only spares the payer a reverting transfer, so a wallet that
|
|
107
|
+
* does not serve `eth_call` still pays.
|
|
108
|
+
*/
|
|
109
|
+
async function tokenBalance(wallet, token, owner) {
|
|
110
|
+
try {
|
|
111
|
+
return await readContract(wallet, { address: token, abi: erc20Abi, functionName: "balanceOf", args: [owner] });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
93
117
|
function isWalletClient(wallet) {
|
|
94
118
|
return "writeContract" in wallet;
|
|
95
119
|
}
|
package/dist/wallet.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wallet.js","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"wallet.js","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phala/pay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Phala Pay checkout: a framework-agnostic client and React bindings",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"crypto",
|
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist",
|
|
43
|
-
"src"
|
|
44
|
-
"CHANGELOG.md"
|
|
43
|
+
"src"
|
|
45
44
|
],
|
|
46
45
|
"publishConfig": {
|
|
47
46
|
"access": "public"
|
package/src/checkout.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { parseClientQuote, quoteIdFromClientSecret, type ClientQuote } from "./q
|
|
|
7
7
|
* - `waiting` for a payment, until `expires_at`;
|
|
8
8
|
* - `seen` once a transfer is in a block, `confirming` once it reaches the route's confirmation
|
|
9
9
|
* (two blocks on Ethereum) and is being valued;
|
|
10
|
-
* - `credited` (
|
|
11
|
-
* credited;
|
|
10
|
+
* - `credited` (typically the quote's `typical_credit_seconds` after paying), or `rejected` for a
|
|
11
|
+
* payment that will not be credited;
|
|
12
12
|
* - `reversed` when a credited payment's transaction left the chain before finality;
|
|
13
13
|
* - `expired` or `canceled` without a payment;
|
|
14
14
|
* - `error` when the client secret is not valid, or the quote's address is not the one your
|
package/src/deposit-address.ts
CHANGED
|
@@ -21,15 +21,29 @@ export interface DepositAddressPayment {
|
|
|
21
21
|
created: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** A deposit address on one network, as the customer's page sees it. */
|
|
25
|
+
export interface ClientDepositAddressNetwork {
|
|
26
|
+
chain_id: number;
|
|
27
|
+
/** The forwarder address to pay on this chain. */
|
|
28
|
+
address: string;
|
|
29
|
+
/**
|
|
30
|
+
* Typical seconds from payment to credit at the confirmation this chain's payments are credited
|
|
31
|
+
* at: about 30 on Ethereum (two blocks), 7 on an OP-stack chain (three blocks), 300 under a
|
|
32
|
+
* `safe` policy, 900 at finality.
|
|
33
|
+
*/
|
|
34
|
+
typical_credit_seconds: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
/**
|
|
25
38
|
* The public view of a deposit address, as `GET /v1/deposit_addresses/{id}?client_secret=…`
|
|
26
|
-
* returns it to a browser: its payments in the last 24 hours, newest first.
|
|
39
|
+
* returns it to a browser: its networks, and its payments in the last 24 hours, newest first.
|
|
27
40
|
*/
|
|
28
41
|
export interface ClientDepositAddress {
|
|
29
42
|
id: string;
|
|
30
43
|
object: "deposit_address";
|
|
31
44
|
livemode: boolean;
|
|
32
45
|
status: "active" | "retired";
|
|
46
|
+
networks: ClientDepositAddressNetwork[];
|
|
33
47
|
payments: DepositAddressPayment[];
|
|
34
48
|
}
|
|
35
49
|
|
|
@@ -79,12 +93,14 @@ export async function retrieveDepositAddress(
|
|
|
79
93
|
/** Validates a response body against the public deposit address view; throws on anything else. */
|
|
80
94
|
export function parseClientDepositAddress(value: unknown): ClientDepositAddress {
|
|
81
95
|
const v = record(value);
|
|
96
|
+
const networks = v["networks"];
|
|
82
97
|
const payments = v["payments"];
|
|
83
98
|
if (
|
|
84
99
|
typeof v["id"] !== "string" ||
|
|
85
100
|
v["object"] !== "deposit_address" ||
|
|
86
101
|
typeof v["livemode"] !== "boolean" ||
|
|
87
102
|
(v["status"] !== "active" && v["status"] !== "retired") ||
|
|
103
|
+
!Array.isArray(networks) ||
|
|
88
104
|
!Array.isArray(payments)
|
|
89
105
|
) {
|
|
90
106
|
throw new TypeError("response does not match the public deposit address view");
|
|
@@ -94,10 +110,29 @@ export function parseClientDepositAddress(value: unknown): ClientDepositAddress
|
|
|
94
110
|
object: "deposit_address",
|
|
95
111
|
livemode: v["livemode"],
|
|
96
112
|
status: v["status"],
|
|
113
|
+
networks: networks.map(parseNetwork),
|
|
97
114
|
payments: payments.map(parsePayment),
|
|
98
115
|
};
|
|
99
116
|
}
|
|
100
117
|
|
|
118
|
+
function parseNetwork(value: unknown): ClientDepositAddressNetwork {
|
|
119
|
+
const n = record(value);
|
|
120
|
+
const seconds = n["typical_credit_seconds"];
|
|
121
|
+
if (
|
|
122
|
+
!Number.isSafeInteger(n["chain_id"]) ||
|
|
123
|
+
typeof n["address"] !== "string" ||
|
|
124
|
+
!Number.isSafeInteger(seconds) ||
|
|
125
|
+
(seconds as number) < 0
|
|
126
|
+
) {
|
|
127
|
+
throw new TypeError("network does not match the public deposit address view");
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
chain_id: n["chain_id"] as number,
|
|
131
|
+
address: n["address"],
|
|
132
|
+
typical_credit_seconds: seconds as number,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
101
136
|
function parsePayment(value: unknown): DepositAddressPayment {
|
|
102
137
|
const p = record(value);
|
|
103
138
|
const status = p["status"];
|
package/src/format.ts
CHANGED
|
@@ -6,7 +6,12 @@ import type { ClientQuote } from "./quote.js";
|
|
|
6
6
|
* `1,273.9185`. Every digit is kept: the service rounds a quote to a few decimals.
|
|
7
7
|
*/
|
|
8
8
|
export function formatTokenAmount(quote: ClientQuote, locale?: string): string {
|
|
9
|
-
|
|
9
|
+
return formatUnitsGrouped(BigInt(quote.amount_atomic), quote.decimals, locale);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** An atomic token amount, grouped for `locale` and without trailing zeros, as `formatTokenAmount`. */
|
|
13
|
+
export function formatUnitsGrouped(atomic: bigint, decimals: number, locale?: string): string {
|
|
14
|
+
const [whole = "0", fraction] = formatUnits(atomic, decimals).split(".");
|
|
10
15
|
const format = new Intl.NumberFormat(locale);
|
|
11
16
|
const grouped = format.format(BigInt(whole));
|
|
12
17
|
if (fraction === undefined) {
|
|
@@ -23,10 +28,23 @@ export function tokenAmount(quote: ClientQuote): string {
|
|
|
23
28
|
|
|
24
29
|
/** The credit in the quote's currency, for example `$25.00`. */
|
|
25
30
|
export function formatAmount(quote: ClientQuote, locale?: string): string {
|
|
31
|
+
return formatMinorAmount(quote, quote.amount, locale);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `minor` units of the quote's currency, for example `$10.00` for 1000. */
|
|
35
|
+
export function formatMinorAmount(quote: ClientQuote, minor: number, locale?: string): string {
|
|
26
36
|
return new Intl.NumberFormat(locale, {
|
|
27
37
|
style: "currency",
|
|
28
38
|
currency: quote.currency.toUpperCase(),
|
|
29
|
-
}).format(
|
|
39
|
+
}).format(minor / 100);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A typical wait, for example `about 30 seconds` or `about 15 minutes`. */
|
|
43
|
+
export function formatWait(seconds: number): string {
|
|
44
|
+
if (seconds < 90) {
|
|
45
|
+
return `about ${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
46
|
+
}
|
|
47
|
+
return `about ${Math.round(seconds / 60)} minutes`;
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
/** `mm:ss` (or `h:mm:ss`) until `expiresAt` Unix seconds, never negative. */
|
package/src/index.ts
CHANGED
package/src/quote.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface ClientQuote {
|
|
|
8
8
|
livemode: boolean;
|
|
9
9
|
/** `open` until a matching payment completes it, it expires by chain time, or it is canceled. */
|
|
10
10
|
status: "open" | "complete" | "expired" | "canceled";
|
|
11
|
-
/**
|
|
11
|
+
/** The quoted credit in the currency's minor unit (US cents). */
|
|
12
12
|
amount: number;
|
|
13
13
|
currency: string;
|
|
14
14
|
/** The token's code, for example `pha`. */
|
|
@@ -25,13 +25,25 @@ export interface ClientQuote {
|
|
|
25
25
|
expires_at: number;
|
|
26
26
|
/**
|
|
27
27
|
* `seen` once in a block (a reorg can remove it), `confirming` while a payment at the route's
|
|
28
|
-
* confirmation is valued and screened, `credited` once it is credited (
|
|
29
|
-
* paying), `rejected` when it will not be, `reversed` when a
|
|
30
|
-
* left the chain before finality (the payment did not happen).
|
|
28
|
+
* confirmation is valued and screened, `credited` once it is credited (typically
|
|
29
|
+
* `typical_credit_seconds` after paying), `rejected` when it will not be, `reversed` when a
|
|
30
|
+
* credited payment's transaction left the chain before finality (the payment did not happen).
|
|
31
31
|
*/
|
|
32
32
|
payment_status: "none" | "seen" | "confirming" | "credited" | "rejected" | "reversed";
|
|
33
33
|
/** Block confirmations while `payment_status` is `seen`, otherwise `null`. */
|
|
34
34
|
confirmations: number | null;
|
|
35
|
+
/**
|
|
36
|
+
* While `payment_status` is `credited`: what was credited, in the currency's minor unit. It
|
|
37
|
+
* differs from `amount` for a payment valued at the market price (another amount, or paid late);
|
|
38
|
+
* otherwise `null`.
|
|
39
|
+
*/
|
|
40
|
+
amount_credited: number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Typical seconds from paying to the credit at the confirmation this quote's payments need:
|
|
43
|
+
* 30 on Ethereum (two blocks), 7 on an OP-stack chain (three blocks), about 300 under a `safe`
|
|
44
|
+
* policy, 900 at finality.
|
|
45
|
+
*/
|
|
46
|
+
typical_credit_seconds: number;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
const QUOTE_STATUSES = ["open", "complete", "expired", "canceled"] as const;
|
|
@@ -53,6 +65,7 @@ export function parseClientQuote(value: unknown): ClientQuote {
|
|
|
53
65
|
}
|
|
54
66
|
const v = value as Record<string, unknown>;
|
|
55
67
|
const confirmations = v["confirmations"];
|
|
68
|
+
const amountCredited = v["amount_credited"];
|
|
56
69
|
if (
|
|
57
70
|
typeof v["id"] !== "string" ||
|
|
58
71
|
v["object"] !== "quote" ||
|
|
@@ -70,7 +83,9 @@ export function parseClientQuote(value: unknown): ClientQuote {
|
|
|
70
83
|
typeof v["payment_uri"] !== "string" ||
|
|
71
84
|
!isSafeInteger(v["expires_at"]) ||
|
|
72
85
|
!oneOf(v["payment_status"], PAYMENT_STATUSES) ||
|
|
73
|
-
!(confirmations === null || isSafeInteger(confirmations))
|
|
86
|
+
!(confirmations === null || isSafeInteger(confirmations)) ||
|
|
87
|
+
!(amountCredited === null || isSafeInteger(amountCredited)) ||
|
|
88
|
+
!isSafeInteger(v["typical_credit_seconds"])
|
|
74
89
|
) {
|
|
75
90
|
throw new TypeError("quote response does not match the public quote view");
|
|
76
91
|
}
|
|
@@ -90,6 +105,8 @@ export function parseClientQuote(value: unknown): ClientQuote {
|
|
|
90
105
|
expires_at: v["expires_at"],
|
|
91
106
|
payment_status: v["payment_status"],
|
|
92
107
|
confirmations,
|
|
108
|
+
amount_credited: amountCredited,
|
|
109
|
+
typical_credit_seconds: v["typical_credit_seconds"],
|
|
93
110
|
};
|
|
94
111
|
}
|
|
95
112
|
|
package/src/react/Checkout.tsx
CHANGED
|
@@ -4,7 +4,14 @@ import { useEffect, useId, useRef, useState, type KeyboardEvent } from "react";
|
|
|
4
4
|
import type { Hash, WalletClient } from "viem";
|
|
5
5
|
import type { CheckoutErrorCode, CheckoutState, CheckoutStatus } from "../checkout.js";
|
|
6
6
|
import { networkName, transactionUrl } from "../chains.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
formatAmount,
|
|
9
|
+
formatCountdown,
|
|
10
|
+
formatMinorAmount,
|
|
11
|
+
formatTokenAmount,
|
|
12
|
+
formatWait,
|
|
13
|
+
tokenAmount,
|
|
14
|
+
} from "../format.js";
|
|
8
15
|
import { quoteTransfer } from "../payment.js";
|
|
9
16
|
import type { ClientQuote } from "../quote.js";
|
|
10
17
|
import {
|
|
@@ -30,13 +37,20 @@ export interface CheckoutProps {
|
|
|
30
37
|
expectedAddress: string;
|
|
31
38
|
/** The service origin, for example `https://topup.example.com`. */
|
|
32
39
|
apiBase: string;
|
|
33
|
-
/** Called once when the payment is credited
|
|
40
|
+
/** Called once when the payment is credited, with the quote: its `amount_credited` is what was
|
|
41
|
+
* credited, which differs from `amount` for a payment of another amount or a late one. Fulfil
|
|
42
|
+
* from the `deposit.credited` webhook, not here. */
|
|
34
43
|
onSuccess?: (quote: ClientQuote) => void;
|
|
35
44
|
/** Called once when the quote expires or is canceled without a payment. */
|
|
36
45
|
onExpire?: (quote: ClientQuote) => void;
|
|
37
46
|
/** Called whenever the checkout's status changes, like Stripe Elements' `onChange`: for example,
|
|
38
47
|
* to hide your own "new payment" control while a payment is `seen` or `confirming`. */
|
|
39
48
|
onChange?: (state: CheckoutState) => void;
|
|
49
|
+
/** Called when the wallet tab could not send the payment, with the `WalletError` and the wallet
|
|
50
|
+
* it tried (the chosen browser wallet's provider, or `walletClient`): for example on
|
|
51
|
+
* `insufficient_balance` (nothing was sent), to offer your own way to fund that wallet. The
|
|
52
|
+
* checkout shows the error's message itself. */
|
|
53
|
+
onWalletError?: (error: WalletError, wallet: WalletClient | EthereumProvider) => void;
|
|
40
54
|
appearance?: Appearance;
|
|
41
55
|
/** Milliseconds between status reads; default 3000. */
|
|
42
56
|
pollInterval?: number;
|
|
@@ -64,6 +78,7 @@ export function Checkout({
|
|
|
64
78
|
onSuccess,
|
|
65
79
|
onExpire,
|
|
66
80
|
onChange,
|
|
81
|
+
onWalletError,
|
|
67
82
|
appearance,
|
|
68
83
|
pollInterval,
|
|
69
84
|
className,
|
|
@@ -133,6 +148,7 @@ export function Checkout({
|
|
|
133
148
|
setTxHash(hash);
|
|
134
149
|
refresh();
|
|
135
150
|
}}
|
|
151
|
+
onWalletError={onWalletError}
|
|
136
152
|
/>
|
|
137
153
|
)}
|
|
138
154
|
</div>
|
|
@@ -182,13 +198,11 @@ function statusMessage(
|
|
|
182
198
|
case "waiting":
|
|
183
199
|
return "Waiting for your payment";
|
|
184
200
|
case "seen":
|
|
185
|
-
return quote
|
|
186
|
-
? "Payment received, crediting in about 30 seconds"
|
|
187
|
-
: `Payment received, ${quote.confirmations} confirmation${quote.confirmations === 1 ? "" : "s"}. Crediting in about 30 seconds`;
|
|
201
|
+
return seenMessage(quote);
|
|
188
202
|
case "confirming":
|
|
189
203
|
return "Payment confirmed on chain, crediting…";
|
|
190
204
|
case "credited":
|
|
191
|
-
return
|
|
205
|
+
return creditedMessage(quote);
|
|
192
206
|
case "rejected":
|
|
193
207
|
return "This payment cannot be credited. Contact support with your transaction.";
|
|
194
208
|
case "reversed":
|
|
@@ -204,6 +218,31 @@ function statusMessage(
|
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
220
|
|
|
221
|
+
function seenMessage(quote: ClientQuote | null): string {
|
|
222
|
+
if (quote === null) {
|
|
223
|
+
return "Payment received, crediting once it is confirmed";
|
|
224
|
+
}
|
|
225
|
+
const wait = formatWait(quote.typical_credit_seconds);
|
|
226
|
+
return quote.confirmations === null
|
|
227
|
+
? `Payment received, crediting in ${wait}`
|
|
228
|
+
: `Payment received, ${quote.confirmations} confirmation${quote.confirmations === 1 ? "" : "s"}. Crediting in ${wait}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** What was credited and, when the payment was valued at the market price, the quote it missed. */
|
|
232
|
+
function creditedMessage(quote: ClientQuote | null): string {
|
|
233
|
+
if (quote?.amount_credited == null) {
|
|
234
|
+
return "Payment credited";
|
|
235
|
+
}
|
|
236
|
+
const credited = formatMinorAmount(quote, quote.amount_credited);
|
|
237
|
+
if (quote.amount_credited < quote.amount) {
|
|
238
|
+
return `Payment credited: ${credited} of ${formatAmount(quote)}`;
|
|
239
|
+
}
|
|
240
|
+
if (quote.amount_credited > quote.amount) {
|
|
241
|
+
return `Payment credited: ${credited}, more than the ${formatAmount(quote)} quoted`;
|
|
242
|
+
}
|
|
243
|
+
return `Payment credited: ${credited}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
207
246
|
function Transaction({ hash, chainId }: { hash: Hash; chainId: number }) {
|
|
208
247
|
const url = transactionUrl(chainId, hash);
|
|
209
248
|
return (
|
|
@@ -226,12 +265,14 @@ function PaymentOptions({
|
|
|
226
265
|
buttonText,
|
|
227
266
|
walletClient,
|
|
228
267
|
onSent,
|
|
268
|
+
onWalletError,
|
|
229
269
|
}: {
|
|
230
270
|
quote: ClientQuote;
|
|
231
271
|
now: number;
|
|
232
272
|
buttonText: string;
|
|
233
273
|
walletClient: WalletClient | undefined;
|
|
234
274
|
onSent: (hash: Hash) => void;
|
|
275
|
+
onWalletError: CheckoutProps["onWalletError"];
|
|
235
276
|
}) {
|
|
236
277
|
const [method, setMethod] = useState<Method>("wallet");
|
|
237
278
|
const id = useId();
|
|
@@ -298,6 +339,7 @@ function PaymentOptions({
|
|
|
298
339
|
buttonText={buttonText}
|
|
299
340
|
walletClient={walletClient}
|
|
300
341
|
onSent={onSent}
|
|
342
|
+
onWalletError={onWalletError}
|
|
301
343
|
/>
|
|
302
344
|
)}
|
|
303
345
|
{method === "qr" && (
|
|
@@ -325,11 +367,13 @@ function WalletPanel({
|
|
|
325
367
|
buttonText,
|
|
326
368
|
walletClient,
|
|
327
369
|
onSent,
|
|
370
|
+
onWalletError,
|
|
328
371
|
}: {
|
|
329
372
|
quote: ClientQuote;
|
|
330
373
|
buttonText: string;
|
|
331
374
|
walletClient: WalletClient | undefined;
|
|
332
375
|
onSent: (hash: Hash) => void;
|
|
376
|
+
onWalletError: CheckoutProps["onWalletError"];
|
|
333
377
|
}) {
|
|
334
378
|
const [discovered, setDiscovered] = useState<Wallet[]>([]);
|
|
335
379
|
const [step, setStep] = useState<WalletStep>({ kind: "idle" });
|
|
@@ -352,6 +396,9 @@ function WalletPanel({
|
|
|
352
396
|
kind: "failed",
|
|
353
397
|
message: error instanceof WalletError ? error.message : "The payment could not be sent",
|
|
354
398
|
});
|
|
399
|
+
if (error instanceof WalletError) {
|
|
400
|
+
onWalletError?.(error, wallet);
|
|
401
|
+
}
|
|
355
402
|
}
|
|
356
403
|
};
|
|
357
404
|
|