@phala/pay 0.1.1 → 0.2.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/CHANGELOG.md +100 -1
- package/README.md +133 -28
- package/dist/checkout.d.ts +37 -7
- package/dist/checkout.d.ts.map +1 -1
- package/dist/checkout.js +55 -11
- package/dist/checkout.js.map +1 -1
- package/dist/client.d.ts +8 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -8
- package/dist/client.js.map +1 -1
- package/dist/deposit-address.d.ts +47 -0
- package/dist/deposit-address.d.ts.map +1 -0
- package/dist/deposit-address.js +88 -0
- package/dist/deposit-address.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/payment.d.ts +43 -1
- package/dist/payment.d.ts.map +1 -1
- package/dist/payment.js +32 -10
- package/dist/payment.js.map +1 -1
- package/dist/quote.d.ts +7 -3
- package/dist/quote.d.ts.map +1 -1
- package/dist/quote.js +3 -1
- package/dist/quote.js.map +1 -1
- package/dist/react/Checkout.d.ts +15 -1
- package/dist/react/Checkout.d.ts.map +1 -1
- package/dist/react/Checkout.js +36 -39
- package/dist/react/Checkout.js.map +1 -1
- package/dist/react/DepositAddress.d.ts +31 -0
- package/dist/react/DepositAddress.d.ts.map +1 -0
- package/dist/react/DepositAddress.js +108 -0
- package/dist/react/DepositAddress.js.map +1 -0
- package/dist/react/Field.d.ts +7 -0
- package/dist/react/Field.d.ts.map +1 -0
- package/dist/react/Field.js +21 -0
- package/dist/react/Field.js.map +1 -0
- package/dist/react/appearance.d.ts +1 -1
- package/dist/react/appearance.d.ts.map +1 -1
- package/dist/react/appearance.js +4 -1
- package/dist/react/appearance.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useCheckout.d.ts +3 -1
- package/dist/react/useCheckout.d.ts.map +1 -1
- package/dist/react/useCheckout.js +4 -3
- package/dist/react/useCheckout.js.map +1 -1
- package/dist/server/addresses.d.ts +78 -0
- package/dist/server/addresses.d.ts.map +1 -0
- package/dist/server/addresses.js +118 -0
- package/dist/server/addresses.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +12 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/sweeps.d.ts +78 -0
- package/dist/server/sweeps.d.ts.map +1 -0
- package/dist/server/sweeps.js +117 -0
- package/dist/server/sweeps.js.map +1 -0
- package/dist/server/webhook.d.ts +60 -0
- package/dist/server/webhook.d.ts.map +1 -0
- package/dist/server/webhook.js +148 -0
- package/dist/server/webhook.js.map +1 -0
- package/dist/wallet.d.ts +10 -8
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +61 -63
- package/dist/wallet.js.map +1 -1
- package/package.json +6 -1
- package/src/checkout.ts +89 -15
- package/src/client.ts +14 -8
- package/src/deposit-address.ts +138 -0
- package/src/index.ts +19 -1
- package/src/payment.ts +72 -10
- package/src/quote.ts +10 -4
- package/src/react/Checkout.tsx +84 -72
- package/src/react/DepositAddress.tsx +213 -0
- package/src/react/Field.tsx +46 -0
- package/src/react/appearance.ts +4 -1
- package/src/react/index.ts +1 -0
- package/src/react/useCheckout.ts +6 -2
- package/src/server/addresses.ts +223 -0
- package/src/server/index.ts +38 -0
- package/src/server/sweeps.ts +197 -0
- package/src/server/webhook.ts +202 -0
- package/src/wallet.ts +74 -68
package/src/wallet.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { createStore } from "mipd";
|
|
1
2
|
import {
|
|
3
|
+
BaseError,
|
|
2
4
|
createWalletClient,
|
|
3
5
|
custom,
|
|
4
6
|
erc20Abi,
|
|
7
|
+
SwitchChainError,
|
|
8
|
+
UserRejectedRequestError,
|
|
5
9
|
type Hash,
|
|
6
10
|
type WalletClient,
|
|
7
11
|
} from "viem";
|
|
@@ -31,57 +35,52 @@ export interface Wallet {
|
|
|
31
35
|
/** The legacy `window.ethereum` provider, offered when no wallet announces itself (EIP-6963). */
|
|
32
36
|
export const INJECTED_WALLET_UUID = "injected";
|
|
33
37
|
|
|
34
|
-
interface AnnounceProviderEvent extends Event {
|
|
35
|
-
detail?: { info?: Partial<WalletInfo>; provider?: EthereumProvider };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
38
|
/**
|
|
39
|
-
* Discovers browser wallets with EIP-6963, falling back to
|
|
40
|
-
* with the current list whenever a wallet announces
|
|
41
|
-
* listening.
|
|
39
|
+
* Discovers browser wallets with EIP-6963 (through mipd's store), falling back to
|
|
40
|
+
* `window.ethereum`, and calls `onChange` with the current list whenever a wallet announces
|
|
41
|
+
* itself. Returns the function that stops listening.
|
|
42
42
|
*/
|
|
43
43
|
export function watchWallets(onChange: (wallets: Wallet[]) => void): () => void {
|
|
44
44
|
if (typeof window === "undefined") {
|
|
45
45
|
onChange([]);
|
|
46
46
|
return () => undefined;
|
|
47
47
|
}
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
provider: injected,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
onChange(wallets);
|
|
48
|
+
const store = createStore();
|
|
49
|
+
const unsubscribe = store.subscribe((details) => onChange(walletsFrom(details)), {
|
|
50
|
+
emitImmediately: true,
|
|
51
|
+
});
|
|
52
|
+
return () => {
|
|
53
|
+
unsubscribe();
|
|
54
|
+
store.destroy();
|
|
59
55
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type Announcement = { info?: Partial<WalletInfo>; provider?: unknown };
|
|
59
|
+
|
|
60
|
+
/** Announcements are untrusted page events: keep well-formed ones, and only data-URI icons. */
|
|
61
|
+
function walletsFrom(announcements: readonly Announcement[]): Wallet[] {
|
|
62
|
+
const wallets: Wallet[] = [];
|
|
63
|
+
for (const { info, provider } of announcements) {
|
|
64
|
+
if (!isProvider(provider) || typeof info?.uuid !== "string" || typeof info.name !== "string") {
|
|
65
|
+
continue;
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
const icon = typeof info.icon === "string" && info.icon.startsWith("data:image/") ? info.icon : "";
|
|
68
|
+
const rdns = typeof info.rdns === "string" ? info.rdns : "";
|
|
69
|
+
wallets.push({ info: { uuid: info.uuid, name: info.name, icon, rdns }, provider });
|
|
70
|
+
}
|
|
71
|
+
// Wallets that predate EIP-6963, such as some in-app mobile browsers, only set `window.ethereum`.
|
|
72
|
+
const injected = (window as { ethereum?: EthereumProvider }).ethereum;
|
|
73
|
+
if (wallets.length === 0 && injected !== undefined) {
|
|
74
|
+
wallets.push({
|
|
75
|
+
info: { uuid: INJECTED_WALLET_UUID, name: "Browser wallet", icon: "", rdns: "" },
|
|
76
|
+
provider: injected,
|
|
78
77
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return ()
|
|
78
|
+
}
|
|
79
|
+
return wallets;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isProvider(value: unknown): value is EthereumProvider {
|
|
83
|
+
return typeof (value as Partial<EthereumProvider> | null | undefined)?.request === "function";
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
export type WalletErrorCode = "rejected" | "no_account" | "wrong_chain" | "failed";
|
|
@@ -99,20 +98,25 @@ export class WalletError extends Error {
|
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
/**
|
|
102
|
-
* Pays a quote from a
|
|
103
|
-
*
|
|
104
|
-
*
|
|
101
|
+
* Pays a quote from a wallet: a viem `WalletClient` (for example wagmi's `useWalletClient()`), or an
|
|
102
|
+
* EIP-1193 provider such as a discovered `Wallet`'s. Connects (unless the client already has an
|
|
103
|
+
* account), switches to (or adds) the quote's chain, and sends the ERC-20 `transfer` that the quote's
|
|
104
|
+
* `payment_uri` states. Resolves with the transaction hash once the wallet has broadcast it; the
|
|
105
|
+
* checkout's status follows the payment from there.
|
|
105
106
|
*/
|
|
106
|
-
export async function payWithWallet(
|
|
107
|
+
export async function payWithWallet(
|
|
108
|
+
wallet: WalletClient | EthereumProvider,
|
|
109
|
+
quote: ClientQuote,
|
|
110
|
+
): Promise<Hash> {
|
|
107
111
|
const transfer = quoteTransfer(quote);
|
|
108
|
-
const
|
|
112
|
+
const client = isWalletClient(wallet) ? wallet : createWalletClient({ transport: custom(wallet) });
|
|
109
113
|
try {
|
|
110
|
-
const
|
|
114
|
+
const account = client.account ?? (await client.requestAddresses())[0];
|
|
111
115
|
if (account === undefined) {
|
|
112
116
|
throw new WalletError("no_account", "The wallet shared no account");
|
|
113
117
|
}
|
|
114
|
-
await ensureChain(
|
|
115
|
-
return await
|
|
118
|
+
await ensureChain(client, transfer.chainId);
|
|
119
|
+
return await client.writeContract({
|
|
116
120
|
account,
|
|
117
121
|
chain: knownChain(transfer.chainId) ?? null,
|
|
118
122
|
address: transfer.token,
|
|
@@ -124,13 +128,17 @@ export async function payWithWallet(provider: EthereumProvider, quote: ClientQuo
|
|
|
124
128
|
if (error instanceof WalletError) {
|
|
125
129
|
throw error;
|
|
126
130
|
}
|
|
127
|
-
if (
|
|
131
|
+
if (isRejection(error)) {
|
|
128
132
|
throw new WalletError("rejected", "The request was rejected in the wallet", { cause: error });
|
|
129
133
|
}
|
|
130
134
|
throw new WalletError("failed", "The wallet could not send the payment", { cause: error });
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
|
|
138
|
+
function isWalletClient(wallet: WalletClient | EthereumProvider): wallet is WalletClient {
|
|
139
|
+
return "writeContract" in wallet;
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
async function ensureChain(wallet: WalletClient, chainId: number): Promise<void> {
|
|
135
143
|
if ((await wallet.getChainId()) === chainId) {
|
|
136
144
|
return;
|
|
@@ -138,10 +146,9 @@ async function ensureChain(wallet: WalletClient, chainId: number): Promise<void>
|
|
|
138
146
|
try {
|
|
139
147
|
await wallet.switchChain({ id: chainId });
|
|
140
148
|
} catch (error) {
|
|
141
|
-
// 4902: the wallet does not know the chain yet.
|
|
142
149
|
const chain = knownChain(chainId);
|
|
143
|
-
if (!
|
|
144
|
-
throw
|
|
150
|
+
if (!isUnknownChain(error) || chain === undefined) {
|
|
151
|
+
throw isRejection(error)
|
|
145
152
|
? error
|
|
146
153
|
: new WalletError("wrong_chain", `Switch your wallet to ${networkName(chainId)}`, {
|
|
147
154
|
cause: error,
|
|
@@ -157,19 +164,18 @@ async function ensureChain(wallet: WalletClient, chainId: number): Promise<void>
|
|
|
157
164
|
}
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return false;
|
|
167
|
+
function isRejection(error: unknown): boolean {
|
|
168
|
+
return error instanceof BaseError && error.walk((e) => e instanceof UserRejectedRequestError) !== null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Whether the wallet does not know the chain (4902). MetaMask Mobile nests that code in an internal
|
|
172
|
+
* error's `data.originalError`, which wagmi's injected connector also unwraps. */
|
|
173
|
+
function isUnknownChain(error: unknown): boolean {
|
|
174
|
+
return (
|
|
175
|
+
error instanceof BaseError &&
|
|
176
|
+
error.walk((e) => {
|
|
177
|
+
const nested = (e as { data?: { originalError?: { code?: unknown } } } | null)?.data;
|
|
178
|
+
return e instanceof SwitchChainError || nested?.originalError?.code === SwitchChainError.code;
|
|
179
|
+
}) !== null
|
|
180
|
+
);
|
|
175
181
|
}
|