@droplinked_inc/wallet-connection 0.1.1

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.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * EVM connector — replaces the v1.0.1 EVMProvider class.
3
+ *
4
+ * Hardening deltas:
5
+ * 1. No remote fetch of contract address (was a single point of supply-chain
6
+ * compromise via apiv3dev.droplinked.com). Caller supplies the address.
7
+ * 2. Wallet selection is strict: MetaMask must self-identify as MetaMask
8
+ * and Coinbase must self-identify as Coinbase. No fallthrough to the
9
+ * umbrella `window.ethereum`.
10
+ * 3. All RPC responses are zod-validated.
11
+ * 4. Login is EIP-712 typed, not personal_sign of a static string.
12
+ * 5. No recursive self-call in `handleWallet` (the original had an
13
+ * uncontrolled recursion that could spin a wallet-permissions popup
14
+ * loop).
15
+ * 6. Bigints throughout — no `any` for `totalPrice`.
16
+ */
17
+ import { Chain, ChainWallet, Network, type ChainProvider, type EvmAddress, type IChainPayment } from '../types.js';
18
+ import { type Eip1193Provider } from '../provider.js';
19
+ import type { Hex } from 'viem';
20
+ /** Minimal ABI fragments — kept inline rather than fetched remotely. */
21
+ declare const ERC20_TRANSFER_ABI: readonly [{
22
+ readonly constant: false;
23
+ readonly inputs: readonly [{
24
+ readonly name: "_to";
25
+ readonly type: "address";
26
+ }, {
27
+ readonly name: "_value";
28
+ readonly type: "uint256";
29
+ }];
30
+ readonly name: "transfer";
31
+ readonly outputs: readonly [{
32
+ readonly name: "";
33
+ readonly type: "bool";
34
+ }];
35
+ readonly stateMutability: "nonpayable";
36
+ readonly type: "function";
37
+ }];
38
+ export interface EvmConnectorOptions {
39
+ readonly chain: Chain;
40
+ readonly network: Network;
41
+ readonly wallet?: ChainWallet;
42
+ /** Required for `payment()` — the droplinked checkout contract address. */
43
+ readonly checkoutContractAddress?: EvmAddress;
44
+ /** Required for typed-data signing. Defaults to globalThis.location.origin. */
45
+ readonly origin?: string;
46
+ }
47
+ export declare class EvmConnector implements ChainProvider {
48
+ readonly chain: Chain;
49
+ readonly network: Network;
50
+ address: EvmAddress;
51
+ wallet: ChainWallet;
52
+ private readonly checkoutContractAddress;
53
+ private readonly origin;
54
+ constructor(opts: EvmConnectorOptions);
55
+ setAddress(address: string): this;
56
+ setWallet(wallet: ChainWallet): this;
57
+ /** Resolve the EIP-1193 provider for the configured wallet. */
58
+ getWalletProvider(): Eip1193Provider;
59
+ /**
60
+ * Ensure the wallet is connected to the expected address and chain.
61
+ * Unlike v1.0.1 this is iterative (max 3 retries) — there is no
62
+ * recursive self-call that can spin a popup loop.
63
+ */
64
+ handleWallet(expectedAddress: string): Promise<void>;
65
+ /**
66
+ * Connect + EIP-712 typed login. Returns the signature; the caller is
67
+ * expected to submit it to the droplinked auth API.
68
+ */
69
+ walletLogin(): Promise<{
70
+ address: EvmAddress;
71
+ signature: string;
72
+ }>;
73
+ /**
74
+ * ERC-20 token transfer.
75
+ *
76
+ * NOTE: This issues a direct `transfer` call. We intentionally do *not*
77
+ * issue an `approve` against an unbounded spender — the drainer-style
78
+ * "approve max uint256 to a router" pattern is rejected at the API
79
+ * boundary. Callers wanting allowance flows must use a different
80
+ * specialized package.
81
+ */
82
+ paymentWithToken(receiver: string, amount: bigint, tokenAddress: string): Promise<string>;
83
+ /**
84
+ * Droplinked checkout contract call. Requires
85
+ * `checkoutContractAddress` to be supplied at construction time —
86
+ * we will never fetch it from a remote endpoint.
87
+ */
88
+ payment(data: IChainPayment): Promise<{
89
+ deploy_hash: string;
90
+ cryptoAmount: bigint;
91
+ }>;
92
+ /**
93
+ * Submit pre-built calldata produced server-side. The calldata is
94
+ * NOT trusted blindly — we enforce two boundary checks before
95
+ * surfacing the transaction to the wallet:
96
+ *
97
+ * 1. `to` MUST equal the connector's configured
98
+ * `checkoutContractAddress`. Sending arbitrary calldata to an
99
+ * arbitrary destination would resurrect the drainer pathway
100
+ * that motivated removing the v1.0.1 remote-ABI fetch.
101
+ * 2. The first 4 bytes of `data` (the function selector) MUST be
102
+ * on the explicit allowlist below. Notably the selectors for
103
+ * `approve(address,uint256)`, `setApprovalForAll(...)`, NFT
104
+ * `safeTransferFrom(...)`, and ERC-20 `permit(...)` are
105
+ * rejected — these are the canonical "drainer" entry points
106
+ * that a compromised checkout API or consumer-side XSS would
107
+ * try to coerce the user into signing.
108
+ *
109
+ * Both checks throw `InvalidTransactionError` BEFORE any wallet
110
+ * prompt appears, so the user is never shown a malicious payload.
111
+ */
112
+ submitRawTransaction(args: {
113
+ readonly to: EvmAddress;
114
+ readonly data: Hex;
115
+ readonly value: bigint;
116
+ readonly gasLimit?: bigint;
117
+ }): Promise<Hex>;
118
+ }
119
+ /**
120
+ * 4-byte function selectors that the droplinked checkout flow is known
121
+ * to call. Anything else is rejected at the boundary.
122
+ *
123
+ * - `0xa9059cbb` — ERC-20 `transfer(address,uint256)`
124
+ * The standard token payment path used by `paymentWithToken`.
125
+ * - `0x6a627842` — droplinked legacy checkout `droplinkedPurchase(...)`
126
+ * Selector matches the calldata blob produced by the droplinked
127
+ * checkout API. Replace if the API rotates to a new function.
128
+ * - `0x` (empty data) — native-currency transfer to the checkout
129
+ * contract. Allowed because there is no selector to spoof; the
130
+ * destination is already pinned by the `to === checkoutContract`
131
+ * check.
132
+ */
133
+ export declare const SUBMIT_RAW_TX_SELECTOR_ALLOWLIST: ReadonlySet<string>;
134
+ /**
135
+ * Drainer selectors callers will sometimes ask the wallet to sign.
136
+ * Listed explicitly so the rejection message names them — when one of
137
+ * these appears in calldata, that is *strong* evidence that the
138
+ * checkout API or the consumer page is compromised.
139
+ */
140
+ export declare const KNOWN_DRAINER_SELECTORS: Readonly<Record<string, string>>;
141
+ /** Returns the calldata blob for `transfer(address,uint256)`. */
142
+ export declare function encodeErc20Transfer(to: EvmAddress, amount: bigint): Hex;
143
+ export { ERC20_TRANSFER_ABI };
144
+ //# sourceMappingURL=evm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/connectors/evm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,KAAK,EAEL,WAAW,EAEX,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAQrB,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC,wEAAwE;AACxE,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;EAYd,CAAC;AAEX,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,uBAAuB,CAAC,EAAE,UAAU,CAAC;IAC9C,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,YAAa,YAAW,aAAa;IAChD,SAAgB,KAAK,EAAE,KAAK,CAAC;IAC7B,SAAgB,OAAO,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,UAAU,CAAgD;IACnE,MAAM,EAAE,WAAW,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAyB;IACjE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,IAAI,EAAE,mBAAmB;IAQrC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKjC,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAUpC,+DAA+D;IAC/D,iBAAiB,IAAI,eAAe;IAUpC;;;;OAIG;IACG,YAAY,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD1D;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IA8BxE;;;;;;;;OAQG;IACG,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC;IA0BlB;;;;OAIG;IACG,OAAO,CACX,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAoBzD;;;;;;;;;;;;;;;;;;;OAmBG;IACG,oBAAoB,CAAC,IAAI,EAAE;QAC/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;QACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;KAC5B,GAAG,OAAO,CAAC,GAAG,CAAC;CAkCjB;AAMD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gCAAgC,EAAE,WAAW,CAAC,MAAM,CAG/D,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOpE,CAAC;AAyCF,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CASvE;AAGD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,330 @@
1
+ /**
2
+ * EVM connector — replaces the v1.0.1 EVMProvider class.
3
+ *
4
+ * Hardening deltas:
5
+ * 1. No remote fetch of contract address (was a single point of supply-chain
6
+ * compromise via apiv3dev.droplinked.com). Caller supplies the address.
7
+ * 2. Wallet selection is strict: MetaMask must self-identify as MetaMask
8
+ * and Coinbase must self-identify as Coinbase. No fallthrough to the
9
+ * umbrella `window.ethereum`.
10
+ * 3. All RPC responses are zod-validated.
11
+ * 4. Login is EIP-712 typed, not personal_sign of a static string.
12
+ * 5. No recursive self-call in `handleWallet` (the original had an
13
+ * uncontrolled recursion that could spin a wallet-permissions popup
14
+ * loop).
15
+ * 6. Bigints throughout — no `any` for `totalPrice`.
16
+ */
17
+ import { ChainPaymentSchema, ChainWallet, EvmAddressSchema, } from '../types.js';
18
+ import { AccountChangedException, ChainMismatchError, InvalidTransactionError, WalletNotFoundException, } from '../errors.js';
19
+ import { getChainMetadata } from '../chains.js';
20
+ import { getAccounts, getChainId, isWalletConnected, requestAccounts, selectCoinbaseProvider, selectMetaMaskProvider, } from '../provider.js';
21
+ import { buildLoginPayload, signLoginPayload, } from '../signing.js';
22
+ /** Minimal ABI fragments — kept inline rather than fetched remotely. */
23
+ const ERC20_TRANSFER_ABI = [
24
+ {
25
+ constant: false,
26
+ inputs: [
27
+ { name: '_to', type: 'address' },
28
+ { name: '_value', type: 'uint256' },
29
+ ],
30
+ name: 'transfer',
31
+ outputs: [{ name: '', type: 'bool' }],
32
+ stateMutability: 'nonpayable',
33
+ type: 'function',
34
+ },
35
+ ];
36
+ export class EvmConnector {
37
+ chain;
38
+ network;
39
+ address = '0x0000000000000000000000000000000000000000';
40
+ wallet;
41
+ checkoutContractAddress;
42
+ origin;
43
+ constructor(opts) {
44
+ this.chain = opts.chain;
45
+ this.network = opts.network;
46
+ this.wallet = opts.wallet ?? ChainWallet.Metamask;
47
+ this.checkoutContractAddress = opts.checkoutContractAddress;
48
+ this.origin = opts.origin ?? resolveOrigin();
49
+ }
50
+ setAddress(address) {
51
+ this.address = EvmAddressSchema.parse(address);
52
+ return this;
53
+ }
54
+ setWallet(wallet) {
55
+ if (wallet !== ChainWallet.Metamask && wallet !== ChainWallet.CoinBase) {
56
+ throw new WalletNotFoundException(`wallet ${wallet} not supported on EVM connector`);
57
+ }
58
+ this.wallet = wallet;
59
+ return this;
60
+ }
61
+ /** Resolve the EIP-1193 provider for the configured wallet. */
62
+ getWalletProvider() {
63
+ if (this.wallet === ChainWallet.Metamask) {
64
+ return selectMetaMaskProvider();
65
+ }
66
+ if (this.wallet === ChainWallet.CoinBase) {
67
+ return selectCoinbaseProvider();
68
+ }
69
+ throw new WalletNotFoundException(`wallet ${this.wallet} not implemented`);
70
+ }
71
+ /**
72
+ * Ensure the wallet is connected to the expected address and chain.
73
+ * Unlike v1.0.1 this is iterative (max 3 retries) — there is no
74
+ * recursive self-call that can spin a popup loop.
75
+ */
76
+ async handleWallet(expectedAddress) {
77
+ const expected = EvmAddressSchema.parse(expectedAddress);
78
+ const provider = this.getWalletProvider();
79
+ const meta = getChainMetadata(this.chain, this.network);
80
+ for (let attempt = 0; attempt < 3; attempt++) {
81
+ let accounts = await getAccounts(provider);
82
+ if (!(await isWalletConnected(provider)) || accounts.length === 0) {
83
+ accounts = await requestAccounts(provider);
84
+ }
85
+ const first = accounts[0];
86
+ if (first === undefined) {
87
+ throw new WalletNotFoundException('wallet returned no accounts');
88
+ }
89
+ if (first.toLowerCase() !== expected) {
90
+ // Ask for permissions once; do NOT recurse forever.
91
+ await provider.request({
92
+ method: 'wallet_requestPermissions',
93
+ params: [{ eth_accounts: {} }],
94
+ });
95
+ continue;
96
+ }
97
+ const currentChainId = await getChainId(provider);
98
+ if (currentChainId.toLowerCase() !== meta.chainIdHex.toLowerCase()) {
99
+ try {
100
+ await provider.request({
101
+ method: 'wallet_switchEthereumChain',
102
+ params: [{ chainId: meta.chainIdHex }],
103
+ });
104
+ }
105
+ catch {
106
+ await provider.request({
107
+ method: 'wallet_addEthereumChain',
108
+ params: [
109
+ {
110
+ chainId: meta.chainIdHex,
111
+ chainName: meta.chainName,
112
+ nativeCurrency: meta.nativeCurrency,
113
+ rpcUrls: meta.rpcUrls,
114
+ },
115
+ ],
116
+ });
117
+ }
118
+ continue;
119
+ }
120
+ return;
121
+ }
122
+ throw new AccountChangedException(`wallet did not converge to ${expected} on ${this.chain}/${this.network}`);
123
+ }
124
+ /**
125
+ * Connect + EIP-712 typed login. Returns the signature; the caller is
126
+ * expected to submit it to the droplinked auth API.
127
+ */
128
+ async walletLogin() {
129
+ const provider = this.getWalletProvider();
130
+ const accountsBefore = await getAccounts(provider);
131
+ const accounts = accountsBefore.length > 0 ? accountsBefore : await requestAccounts(provider);
132
+ const first = accounts[0];
133
+ if (first === undefined) {
134
+ throw new WalletNotFoundException('wallet returned no accounts');
135
+ }
136
+ const address = EvmAddressSchema.parse(first);
137
+ const meta = getChainMetadata(this.chain, this.network);
138
+ const currentChainId = await getChainId(provider);
139
+ if (currentChainId.toLowerCase() !== meta.chainIdHex.toLowerCase()) {
140
+ throw new ChainMismatchError(`wallet on chain ${currentChainId} but ${meta.chainIdHex} expected`);
141
+ }
142
+ const payload = buildLoginPayload({
143
+ address,
144
+ chainId: meta.chainIdNumber,
145
+ origin: this.origin,
146
+ expiresInSeconds: 60 * 10, // 10 minutes
147
+ });
148
+ const signature = await signLoginPayload(provider, payload);
149
+ this.address = address;
150
+ return { address, signature };
151
+ }
152
+ /**
153
+ * ERC-20 token transfer.
154
+ *
155
+ * NOTE: This issues a direct `transfer` call. We intentionally do *not*
156
+ * issue an `approve` against an unbounded spender — the drainer-style
157
+ * "approve max uint256 to a router" pattern is rejected at the API
158
+ * boundary. Callers wanting allowance flows must use a different
159
+ * specialized package.
160
+ */
161
+ async paymentWithToken(receiver, amount, tokenAddress) {
162
+ const recv = EvmAddressSchema.parse(receiver);
163
+ const token = EvmAddressSchema.parse(tokenAddress);
164
+ if (typeof amount !== 'bigint' || amount <= 0n) {
165
+ throw new TypeError('amount must be a positive bigint');
166
+ }
167
+ await this.handleWallet(this.address);
168
+ const provider = this.getWalletProvider();
169
+ const data = encodeErc20Transfer(recv, amount);
170
+ const txHash = await provider.request({
171
+ method: 'eth_sendTransaction',
172
+ params: [
173
+ {
174
+ from: this.address,
175
+ to: token,
176
+ data,
177
+ },
178
+ ],
179
+ });
180
+ if (typeof txHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/u.test(txHash)) {
181
+ throw new Error('wallet returned malformed tx hash');
182
+ }
183
+ return txHash;
184
+ }
185
+ /**
186
+ * Droplinked checkout contract call. Requires
187
+ * `checkoutContractAddress` to be supplied at construction time —
188
+ * we will never fetch it from a remote endpoint.
189
+ */
190
+ async payment(data) {
191
+ const parsed = ChainPaymentSchema.parse(data);
192
+ if (this.checkoutContractAddress === undefined) {
193
+ throw new Error('payment() requires checkoutContractAddress to be set; remote address discovery has been removed');
194
+ }
195
+ await this.handleWallet(this.address);
196
+ // We do NOT decode/encode the legacy droplinkedPurchase ABI here —
197
+ // that surface is feature-frozen and consumers (Next-ShopFront) drive
198
+ // it via a server-built calldata blob. Surface the lower-level
199
+ // eth_sendTransaction primitive so callers retain control.
200
+ void parsed;
201
+ throw new Error('EVMConnector.payment(): direct ABI encoding intentionally removed. ' +
202
+ 'Use submitRawTransaction() with calldata produced by the droplinked checkout API.');
203
+ }
204
+ /**
205
+ * Submit pre-built calldata produced server-side. The calldata is
206
+ * NOT trusted blindly — we enforce two boundary checks before
207
+ * surfacing the transaction to the wallet:
208
+ *
209
+ * 1. `to` MUST equal the connector's configured
210
+ * `checkoutContractAddress`. Sending arbitrary calldata to an
211
+ * arbitrary destination would resurrect the drainer pathway
212
+ * that motivated removing the v1.0.1 remote-ABI fetch.
213
+ * 2. The first 4 bytes of `data` (the function selector) MUST be
214
+ * on the explicit allowlist below. Notably the selectors for
215
+ * `approve(address,uint256)`, `setApprovalForAll(...)`, NFT
216
+ * `safeTransferFrom(...)`, and ERC-20 `permit(...)` are
217
+ * rejected — these are the canonical "drainer" entry points
218
+ * that a compromised checkout API or consumer-side XSS would
219
+ * try to coerce the user into signing.
220
+ *
221
+ * Both checks throw `InvalidTransactionError` BEFORE any wallet
222
+ * prompt appears, so the user is never shown a malicious payload.
223
+ */
224
+ async submitRawTransaction(args) {
225
+ if (this.checkoutContractAddress === undefined) {
226
+ throw new InvalidTransactionError('submitRawTransaction() requires checkoutContractAddress to be configured on the connector');
227
+ }
228
+ const to = EvmAddressSchema.parse(args.to);
229
+ if (to !== this.checkoutContractAddress.toLowerCase()) {
230
+ throw new InvalidTransactionError(`submitRawTransaction(): destination ${to} does not match configured checkout contract ${this.checkoutContractAddress}`);
231
+ }
232
+ assertCalldataSelectorAllowed(args.data);
233
+ await this.handleWallet(this.address);
234
+ const provider = this.getWalletProvider();
235
+ const params = {
236
+ from: this.address,
237
+ to: args.to,
238
+ data: args.data,
239
+ value: `0x${args.value.toString(16)}`,
240
+ };
241
+ if (args.gasLimit !== undefined) {
242
+ params['gas'] = `0x${args.gasLimit.toString(16)}`;
243
+ }
244
+ const txHash = await provider.request({
245
+ method: 'eth_sendTransaction',
246
+ params: [params],
247
+ });
248
+ if (typeof txHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/u.test(txHash)) {
249
+ throw new Error('wallet returned malformed tx hash');
250
+ }
251
+ return txHash;
252
+ }
253
+ }
254
+ /* -------------------------------------------------------------------------- */
255
+ /* submitRawTransaction calldata-selector allowlist */
256
+ /* -------------------------------------------------------------------------- */
257
+ /**
258
+ * 4-byte function selectors that the droplinked checkout flow is known
259
+ * to call. Anything else is rejected at the boundary.
260
+ *
261
+ * - `0xa9059cbb` — ERC-20 `transfer(address,uint256)`
262
+ * The standard token payment path used by `paymentWithToken`.
263
+ * - `0x6a627842` — droplinked legacy checkout `droplinkedPurchase(...)`
264
+ * Selector matches the calldata blob produced by the droplinked
265
+ * checkout API. Replace if the API rotates to a new function.
266
+ * - `0x` (empty data) — native-currency transfer to the checkout
267
+ * contract. Allowed because there is no selector to spoof; the
268
+ * destination is already pinned by the `to === checkoutContract`
269
+ * check.
270
+ */
271
+ export const SUBMIT_RAW_TX_SELECTOR_ALLOWLIST = new Set([
272
+ '0xa9059cbb',
273
+ '0x6a627842',
274
+ ]);
275
+ /**
276
+ * Drainer selectors callers will sometimes ask the wallet to sign.
277
+ * Listed explicitly so the rejection message names them — when one of
278
+ * these appears in calldata, that is *strong* evidence that the
279
+ * checkout API or the consumer page is compromised.
280
+ */
281
+ export const KNOWN_DRAINER_SELECTORS = {
282
+ '0x095ea7b3': 'approve(address,uint256)',
283
+ '0xa22cb465': 'setApprovalForAll(address,bool)',
284
+ '0x42842e0e': 'safeTransferFrom(address,address,uint256) (NFT)',
285
+ '0xb88d4fde': 'safeTransferFrom(address,address,uint256,bytes) (NFT)',
286
+ '0x23b872dd': 'transferFrom(address,address,uint256)',
287
+ '0xd505accf': 'permit(address,address,uint256,uint256,uint8,bytes32,bytes32)',
288
+ };
289
+ function assertCalldataSelectorAllowed(data) {
290
+ if (typeof data !== 'string' || !/^0x[a-fA-F0-9]*$/u.test(data)) {
291
+ throw new InvalidTransactionError('submitRawTransaction(): data must be a 0x-prefixed hex string');
292
+ }
293
+ // Native-currency transfer (empty data) is allowed — destination is
294
+ // already pinned to the checkout contract.
295
+ if (data === '0x' || data.length === 2) {
296
+ return;
297
+ }
298
+ if (data.length < 10) {
299
+ throw new InvalidTransactionError(`submitRawTransaction(): calldata too short to contain a 4-byte selector (got ${data.length - 2} hex chars)`);
300
+ }
301
+ const selector = data.slice(0, 10).toLowerCase();
302
+ const drainer = KNOWN_DRAINER_SELECTORS[selector];
303
+ if (drainer !== undefined) {
304
+ throw new InvalidTransactionError(`submitRawTransaction(): refused drainer-style selector ${selector} (${drainer})`);
305
+ }
306
+ if (!SUBMIT_RAW_TX_SELECTOR_ALLOWLIST.has(selector)) {
307
+ throw new InvalidTransactionError(`submitRawTransaction(): selector ${selector} is not on the allowlist`);
308
+ }
309
+ }
310
+ function resolveOrigin() {
311
+ const g = globalThis;
312
+ return g.location?.origin ?? 'https://droplinked.com';
313
+ }
314
+ /* -------------------------------------------------------------------------- */
315
+ /* Internal: hand-written ERC-20 transfer encoder */
316
+ /* -------------------------------------------------------------------------- */
317
+ /** Returns the calldata blob for `transfer(address,uint256)`. */
318
+ export function encodeErc20Transfer(to, amount) {
319
+ if (typeof amount !== 'bigint' || amount < 0n) {
320
+ throw new TypeError('amount must be a non-negative bigint');
321
+ }
322
+ // keccak256('transfer(address,uint256)').slice(0,4) = 0xa9059cbb
323
+ const selector = 'a9059cbb';
324
+ const addressPart = to.toLowerCase().replace(/^0x/u, '').padStart(64, '0');
325
+ const amountPart = amount.toString(16).padStart(64, '0');
326
+ return `0x${selector}${addressPart}${amountPart}`;
327
+ }
328
+ // Re-export the ABI fragment for callers that want to introspect.
329
+ export { ERC20_TRANSFER_ABI };
330
+ //# sourceMappingURL=evm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evm.js","sourceRoot":"","sources":["../../src/connectors/evm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Phantom (Solana) connector.
3
+ *
4
+ * Hardening deltas vs. v1.0.1:
5
+ * 1. No `window.open('https://phantom.app/', '_blank')` redirect — the
6
+ * original silently popped a new tab when Phantom was missing, which
7
+ * is a phishing-friendly UX. We throw a typed error and let the host
8
+ * app decide what to render.
9
+ * 2. No `while(true) { sleep; getParsedTransaction }` busy-loop — the
10
+ * original could hang a tab forever if the network stalled. We
11
+ * surface the unsigned-transaction hash and let the caller poll.
12
+ * 3. No `console.log(error); throw error` — errors propagate without
13
+ * leaking through stdout.
14
+ * 4. Signature buffer is returned as base64 rather than the raw
15
+ * Uint8Array — same format as MetaMask EIP-712 output so server
16
+ * verification is uniform.
17
+ * 5. Login signs a SIWS-style (Sign-In With Solana, https://siws.xyz/)
18
+ * JSON payload with domain, nonce, chainId, issuedAt,
19
+ * expirationTime — NOT a static plaintext. The signature is
20
+ * bound to origin + chain + time and cannot be replayed across
21
+ * sites or sessions. Verification is provided alongside.
22
+ */
23
+ import { z } from 'zod';
24
+ import { Chain, ChainWallet, Network, type ChainProvider, type IChainPayment } from '../types.js';
25
+ /**
26
+ * Default human-readable statement embedded in the signed payload so
27
+ * Phantom's prompt explains *why* the user is signing.
28
+ */
29
+ export declare const DEFAULT_SOLANA_LOGIN_STATEMENT = "Sign in to droplinked. This signature does not authorize any token transfer.";
30
+ /** Allowed Solana chain identifiers; `chain` is bound into the payload. */
31
+ export declare const SOLANA_CHAIN_IDS: readonly ["solana-mainnet", "solana-devnet", "solana-testnet"];
32
+ export type SolanaChainId = (typeof SOLANA_CHAIN_IDS)[number];
33
+ /** Hard upper bound on payload TTL — refused at build time. */
34
+ export declare const SOLANA_LOGIN_MAX_TTL_SECONDS: number;
35
+ /** Default TTL when caller does not specify one. */
36
+ export declare const SOLANA_LOGIN_DEFAULT_TTL_SECONDS: number;
37
+ export declare const SolanaLoginPayloadSchema: z.ZodObject<{
38
+ domain: z.ZodString;
39
+ address: z.ZodString;
40
+ chain: z.ZodEnum<["solana-mainnet", "solana-devnet", "solana-testnet"]>;
41
+ nonce: z.ZodString;
42
+ issuedAt: z.ZodString;
43
+ expirationTime: z.ZodString;
44
+ statement: z.ZodString;
45
+ }, "strip", z.ZodTypeAny, {
46
+ domain: string;
47
+ address: string;
48
+ statement: string;
49
+ nonce: string;
50
+ issuedAt: string;
51
+ expirationTime: string;
52
+ chain: "solana-mainnet" | "solana-devnet" | "solana-testnet";
53
+ }, {
54
+ domain: string;
55
+ address: string;
56
+ statement: string;
57
+ nonce: string;
58
+ issuedAt: string;
59
+ expirationTime: string;
60
+ chain: "solana-mainnet" | "solana-devnet" | "solana-testnet";
61
+ }>;
62
+ export type SolanaLoginPayload = z.infer<typeof SolanaLoginPayloadSchema>;
63
+ export interface BuildSolanaLoginPayloadArgs {
64
+ readonly address: string;
65
+ readonly chain: SolanaChainId;
66
+ readonly origin: string;
67
+ readonly statement?: string;
68
+ readonly expiresInSeconds?: number;
69
+ }
70
+ /**
71
+ * Build a SIWS-style login payload binding the signature to
72
+ * (domain, chain, nonce, issuedAt, expirationTime). Refuses any TTL
73
+ * over `SOLANA_LOGIN_MAX_TTL_SECONDS`.
74
+ */
75
+ export declare function buildSolanaLoginPayload(args: BuildSolanaLoginPayloadArgs): SolanaLoginPayload;
76
+ /**
77
+ * Canonicalize the payload to a deterministic JSON string with sorted
78
+ * keys. This is what gets signed — same string on both sides of the
79
+ * verification boundary.
80
+ */
81
+ export declare function canonicalizeSolanaLoginPayload(payload: SolanaLoginPayload): string;
82
+ /** Decode a base58-encoded string into bytes. Throws on invalid input. */
83
+ export declare function base58Decode(input: string): Uint8Array;
84
+ export interface VerifySolanaLoginArgs {
85
+ readonly payload: SolanaLoginPayload;
86
+ /** base64-encoded ed25519 signature returned by `walletLogin()`. */
87
+ readonly signature: string;
88
+ readonly expectedAddress: string;
89
+ readonly expectedChain: SolanaChainId;
90
+ readonly expectedOrigin: string;
91
+ readonly nowMs?: number;
92
+ }
93
+ /**
94
+ * Verify a Phantom (Solana) SIWS-style login signature.
95
+ *
96
+ * Checks (in order):
97
+ * 1. Payload shape validates against `SolanaLoginPayloadSchema`.
98
+ * 2. `payload.chain === expectedChain` (cross-network replay refused).
99
+ * 3. `payload.domain === host(expectedOrigin)` (origin-spoofing refused).
100
+ * 4. `payload.address === expectedAddress`.
101
+ * 5. `now < expirationTime` AND `issuedAt <= now` (freshness).
102
+ * 6. The base64 signature is a valid ed25519 signature over
103
+ * `utf8(canonicalizeSolanaLoginPayload(payload))` for the public
104
+ * key derived from `expectedAddress` (base58-decoded).
105
+ *
106
+ * Note: nonce replay tracking is the responsibility of the server-side
107
+ * caller — this function does not maintain a seen-nonces store. The
108
+ * canonical pattern is to record `(payload.nonce, payload.address)`
109
+ * in a short-lived store keyed by `expirationTime` and reject any
110
+ * second presentation.
111
+ */
112
+ export declare function verifySolanaLoginSignature(args: VerifySolanaLoginArgs): Promise<void>;
113
+ export interface PhantomConnectorOptions {
114
+ readonly network: Network;
115
+ /** Origin to bind into the login payload. Defaults to globalThis.location.origin. */
116
+ readonly origin?: string;
117
+ /**
118
+ * Solana chain identifier to bind into the login payload. Defaults
119
+ * to `solana-mainnet` on Network.MAINNET and `solana-devnet` on
120
+ * Network.TESTNET.
121
+ */
122
+ readonly chainId?: SolanaChainId;
123
+ }
124
+ export declare class PhantomConnector implements ChainProvider {
125
+ readonly chain: Chain;
126
+ readonly network: Network;
127
+ address: string;
128
+ wallet: ChainWallet;
129
+ private readonly origin;
130
+ private readonly chainId;
131
+ constructor(networkOrOptions: Network | PhantomConnectorOptions);
132
+ setAddress(address: string): this;
133
+ setWallet(wallet: ChainWallet): this;
134
+ /**
135
+ * Connect + SIWS-style typed login. Builds a fresh payload with a
136
+ * 256-bit nonce, binds it to origin + chain + time, asks Phantom to
137
+ * sign the canonicalized JSON, and returns the payload alongside the
138
+ * base64 signature. Server-side verification uses
139
+ * `verifySolanaLoginSignature`.
140
+ *
141
+ * Replaces the v1.0.1 / pre-audit static-plaintext signMessage path,
142
+ * which was replayable across origins, chains, and sessions.
143
+ */
144
+ walletLogin(): Promise<{
145
+ address: string;
146
+ signature: string;
147
+ payload: SolanaLoginPayload;
148
+ }>;
149
+ /**
150
+ * SPL token transfer is intentionally not implemented in this minimal
151
+ * recover+harden. The original v1.0.1 relied on the long-deprecated
152
+ * `@solana/spl-token` v0.1.x Token-class API (removed in v0.2). Re-
153
+ * implementing it correctly against the current `@solana/spl-token` v0.4
154
+ * APIs is out of scope for the first PR; it will land in a follow-up.
155
+ *
156
+ * The method throws a typed error so consumers fail loudly rather than
157
+ * silently mis-routing a transfer.
158
+ */
159
+ paymentWithToken(_receiver: string, _amount: bigint, _tokenAddress: string): Promise<string>;
160
+ payment(_data: IChainPayment): Promise<{
161
+ deploy_hash: string;
162
+ cryptoAmount: bigint;
163
+ }>;
164
+ }
165
+ /** base64-encode a Uint8Array. Browser-safe; no Buffer dependency. */
166
+ export declare function toBase64(bytes: Uint8Array): string;
167
+ //# sourceMappingURL=phantom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phantom.d.ts","sourceRoot":"","sources":["../../src/connectors/phantom.ts"],"names":[],"mappings":"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"}