@chaindaddy/js 0.1.0 → 0.3.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/dist/verify.js ADDED
@@ -0,0 +1,342 @@
1
+ // src/checkouts.json
2
+ var checkouts_default = {
3
+ generatedBy: "js/scripts/gen-checkouts.mjs from deployments/*.json; do not edit by hand. Regenerate whenever a checkout is deployed.",
4
+ checkouts: {
5
+ "eip155:1": [
6
+ "0x1825e21c72ebd14a683d56200ee834a371313bf1",
7
+ "0x7a5f3e0b9cf37cc6757216f843a9f9f9b4324d17",
8
+ "0x3e79d8041f578b2721c03cb8c086798d76ea7709",
9
+ "0x2428edd69cdc837d77bec390ec6359b7fac430a1"
10
+ ],
11
+ "eip155:56": [
12
+ "0x2921755ce671b98e06f78389e20d6f971af5c77a",
13
+ "0x1840851a5931e43f4fd73096987025be00e6a1b6",
14
+ "0x7a5f3e0b9cf37cc6757216f843a9f9f9b4324d17",
15
+ "0x1825e21c72ebd14a683d56200ee834a371313bf1"
16
+ ],
17
+ "eip155:97": [
18
+ "0xe29e6bb43f3d5074f2e54a4524635d7d96708577",
19
+ "0x0e7f3f99d23e8c950b0ed9885a008ff371ce34cd",
20
+ "0x24d60fc4c0e40e0665a00767a857669e25b8ebf2",
21
+ "0x54be2770892c2e73d47ad854365c346bf63d5ba7"
22
+ ],
23
+ "eip155:100": [
24
+ "0x44a14d4da51af4e7caad01aaadc6afe731f7d316",
25
+ "0x90fa09304ac71d16a7928ff5a6cd73cd5eafb207"
26
+ ],
27
+ "eip155:137": [
28
+ "0x8d06e5e72510b9e30689e0fad7d8df7fc3a03907",
29
+ "0x169f7562decb2999af40ac7079022afcc131c3bc",
30
+ "0x7225ffab009292d5b46da31ca0f030fae5ce36eb",
31
+ "0x0dcd5004bd92c745b21dd64b02d0db219163dbbd"
32
+ ],
33
+ "eip155:4663": [
34
+ "0xaff354c143227347b3e3a108eb7e13565c04ff6e",
35
+ "0x2abc5bcb3a2595bc3bfd643be823b548277aa658",
36
+ "0x639c756ffce88288db3e0cd58b0228632455e842",
37
+ "0x1840851a5931e43f4fd73096987025be00e6a1b6"
38
+ ],
39
+ "eip155:8453": [
40
+ "0x3e79d8041f578b2721c03cb8c086798d76ea7709",
41
+ "0x1825e21c72ebd14a683d56200ee834a371313bf1",
42
+ "0x70c172a82dd320ac8711b9645f3875db830fe563",
43
+ "0x81087101ae075020ccc4595b099cbfb2a752b6d1"
44
+ ],
45
+ "eip155:10200": [
46
+ "0xbd0f5bc1d248759696d5cb66e4190fd505e3a76c",
47
+ "0xe484e68bd019902de5050c8cf7f3379528f672d3",
48
+ "0xdc2e5ce751f45ebed24df7766b7ab0ab1b03c37b",
49
+ "0xcfa4a619e0cb26956a88224aea680100fc7689e3"
50
+ ],
51
+ "eip155:42161": [
52
+ "0xfb821475b97a95a54d09016a62f490e0f2bd2c2f",
53
+ "0x0cbf237af2c3fb2442628c12a10d022ba452a49f",
54
+ "0x4258da759dd30f56f019f4020969c228c58cfca8",
55
+ "0x377e606ce0522f91a32095c97c52e4ef4a33f8e6"
56
+ ],
57
+ "eip155:43113": [
58
+ "0x5d9cb9a21cc455767792b64c7a5153255d915048",
59
+ "0x4b4c4c900135131db09c6d7a6432a1339e1e4508",
60
+ "0xd9fc410766df9149bb39091512d68702dd81da1d",
61
+ "0xe13528d58e35f3b4451dd44a6733f63192d966d2"
62
+ ],
63
+ "eip155:43114": [
64
+ "0x2921755ce671b98e06f78389e20d6f971af5c77a",
65
+ "0x1840851a5931e43f4fd73096987025be00e6a1b6"
66
+ ],
67
+ "eip155:46630": [
68
+ "0x468ac90058cb9344542c5f9c21f014cc60365f40",
69
+ "0x476895a3fe92d8ec9fd70b2a243d60bf4949c829",
70
+ "0xe59faf03129588161de83525e23a8c9a85cc57f4",
71
+ "0x10cc44c747add456a022486897757bcce815c425"
72
+ ],
73
+ "eip155:80002": [
74
+ "0xabe26c6a59ff8a07a57d310150bbbd7dc5b60960",
75
+ "0x4e017dafff03ee85b78c0fcac1f38249ac7bf730",
76
+ "0x4a81e9cfbfda56d6c5c840a4c8d2e7c7f5e520b7",
77
+ "0xfc75c3cab2aa4fa471afffe44ef7fdc79443115d"
78
+ ],
79
+ "eip155:84532": [
80
+ "0xa733934c37a10fbfd574a986ec6b78082f444a1c",
81
+ "0x5fc22e707d5583d729c9477244a12f994289396c",
82
+ "0x423b5426b002e7a5840c325db63ade3e8933a3cb",
83
+ "0x129a328e7cd88a4c25dc6aeeab72995f06cff7ad"
84
+ ],
85
+ "eip155:421614": [
86
+ "0xb92f6f2d1e0301c5870cb0c1437c94068851b180",
87
+ "0x1cf15debaa2d68a12822a8bc6e3fbc5c24f345c8",
88
+ "0xfa4f1753ec974b609ac2755357474952740098ed",
89
+ "0x6f517ca1e1aadd9cb02d1a826927d42ac1ccf7a9"
90
+ ],
91
+ "eip155:11155111": [
92
+ "0xe97aaadd52bf3a16f012898656c16f81a921965b",
93
+ "0x997832bde33b0c7b5af50545807cd07ac36dc4b4",
94
+ "0x4501836080b3c6936ea4796542dcbb344d843c6c",
95
+ "0xf1d53237940f1c48f4b305d8b14a9b03d8252dcc"
96
+ ]
97
+ }
98
+ };
99
+
100
+ // src/confirmations.json
101
+ var confirmations_default = {
102
+ generatedBy: "go test ./pkg/iap -run TestJSConfirmationFloorsMatchTheServer (UPDATE_VERIFY_VECTORS=1 rewrites this file); do not edit by hand",
103
+ confirmations: {
104
+ "eip155:1": 12,
105
+ "eip155:100": 10,
106
+ "eip155:10200": 2,
107
+ "eip155:11155111": 2,
108
+ "eip155:137": 64,
109
+ "eip155:42161": 20,
110
+ "eip155:421614": 2,
111
+ "eip155:43113": 2,
112
+ "eip155:43114": 2,
113
+ "eip155:4663": 20,
114
+ "eip155:46630": 2,
115
+ "eip155:56": 15,
116
+ "eip155:80002": 2,
117
+ "eip155:8453": 5,
118
+ "eip155:84532": 2,
119
+ "eip155:97": 2
120
+ }
121
+ };
122
+
123
+ // src/verify.ts
124
+ function frozen(table, each) {
125
+ const out = /* @__PURE__ */ Object.create(null);
126
+ for (const key of Object.keys(table)) out[key] = each(table[key]);
127
+ return Object.freeze(out);
128
+ }
129
+ var CHECKOUTS = frozen(checkouts_default.checkouts, (a) => Object.freeze([...a]));
130
+ var MIN_CONFIRMATIONS = frozen(confirmations_default.confirmations, (n) => n);
131
+ var DEFAULT_MIN_CONFIRMATIONS = 12;
132
+ var NATIVE_TOKEN = "0x0000000000000000000000000000000000000000";
133
+ var HASH_ORDER = "9ba31b09";
134
+ var PAID_BY = "b2b9adc2";
135
+ var MAX_UINT256 = (1n << 256n) - 1n;
136
+ var MAX_PAYEES = 32;
137
+ var VerifyError = class extends Error {
138
+ constructor(code, message) {
139
+ super(message);
140
+ this.name = "VerifyError";
141
+ this.code = code;
142
+ }
143
+ };
144
+ function isObject(v) {
145
+ return typeof v === "object" && v !== null && !Array.isArray(v);
146
+ }
147
+ function address(v, what, code) {
148
+ if (typeof v !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(v)) throw new VerifyError(code, `${what} is not an address`);
149
+ return v.toLowerCase();
150
+ }
151
+ function bytes32(v, what, code) {
152
+ if (typeof v !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(v)) throw new VerifyError(code, `${what} is not 32 bytes of hex`);
153
+ return v.toLowerCase();
154
+ }
155
+ function uint256(v, what, code, allowNumber = false) {
156
+ let n;
157
+ if (typeof v === "bigint") n = v;
158
+ else if (typeof v === "string" && /^[0-9]{1,78}$/.test(v)) n = BigInt(v);
159
+ else if (allowNumber && typeof v === "number" && Number.isSafeInteger(v)) n = BigInt(v);
160
+ else throw new VerifyError(code, `${what} must be ${allowNumber ? "a whole number" : "a bigint or a decimal string, never a number"}`);
161
+ if (n < 0n || n > MAX_UINT256) throw new VerifyError(code, `${what} does not fit in a uint256`);
162
+ return n;
163
+ }
164
+ function chain(v, what, code) {
165
+ if (typeof v === "number" && Number.isSafeInteger(v) && v > 0) return `eip155:${v}`;
166
+ if (typeof v === "string") {
167
+ const m = /^(?:eip155:)?([1-9][0-9]{0,15})$/.exec(v);
168
+ if (m) return `eip155:${m[1]}`;
169
+ }
170
+ throw new VerifyError(code, `${what} is not an EVM chain id ("eip155:8453" or 8453)`);
171
+ }
172
+ function parseOrder(v, code) {
173
+ if (!isObject(v)) throw new VerifyError(code, "order is missing");
174
+ const { payees, amounts } = v;
175
+ if (!Array.isArray(payees) || !Array.isArray(amounts) || payees.length < 1 || payees.length > MAX_PAYEES || payees.length !== amounts.length) {
176
+ throw new VerifyError(code, "order.payees and order.amounts must be lists of the same length, one entry or more");
177
+ }
178
+ return {
179
+ orderId: bytes32(v.orderId, "order.orderId", code),
180
+ token: address(v.token, "order.token", code),
181
+ // Array.from, not map: map skips the holes of a sparse array, and the two lists would then disagree with their lengths.
182
+ payees: Array.from(payees, (p, i) => address(p, `order.payees[${i}]`, code)),
183
+ // The wire prints amounts as decimal strings. A JSON number may already have lost precision, so it is refused.
184
+ amounts: Array.from(amounts, (a, i) => uint256(a, `order.amounts[${i}]`, code)),
185
+ deadline: uint256(v.deadline, "order.deadline", code, true)
186
+ };
187
+ }
188
+ var word = (n) => n.toString(16).padStart(64, "0");
189
+ var addressWord = (a) => a.slice(2).padStart(64, "0");
190
+ function encodeParsedOrder(o) {
191
+ const n = BigInt(o.payees.length);
192
+ return "0x" + HASH_ORDER + word(32n) + // where the tuple starts
193
+ o.orderId.slice(2) + addressWord(o.token) + word(160n) + // where payees starts, from the tuple: after its five head words
194
+ word(160n + 32n * (n + 1n)) + // where amounts starts: after payees' length and entries
195
+ word(o.deadline) + word(n) + o.payees.map(addressWord).join("") + word(n) + o.amounts.map(word).join("");
196
+ }
197
+ function encodeHashOrder(order) {
198
+ return encodeParsedOrder(parseOrder(order, "bad_verification"));
199
+ }
200
+ function encodePaidBy(orderHash) {
201
+ return "0x" + PAID_BY + bytes32(orderHash, "orderHash", "bad_verification").slice(2);
202
+ }
203
+ function checkoutsOn(chainId) {
204
+ return CHECKOUTS[chainId] ?? [];
205
+ }
206
+ function isKnownCheckout(chainId, checkout) {
207
+ try {
208
+ return checkoutsOn(chain(chainId, "chainId", "bad_options")).includes(address(checkout, "checkout", "bad_options"));
209
+ } catch {
210
+ return false;
211
+ }
212
+ }
213
+ function transport(o) {
214
+ if (typeof o.request === "function" && o.rpcUrl === void 0) {
215
+ const request = o.request;
216
+ return async (method, params) => {
217
+ try {
218
+ return await request({ method, params });
219
+ } catch (e) {
220
+ throw new VerifyError("rpc_error", `${method} failed: ${e instanceof Error ? e.message : String(e)}`);
221
+ }
222
+ };
223
+ }
224
+ if (typeof o.rpcUrl !== "string" || o.rpcUrl === "" || o.request !== void 0) {
225
+ throw new VerifyError("bad_options", "pass rpcUrl (your JSON-RPC endpoint) or request, one of the two");
226
+ }
227
+ const url = o.rpcUrl;
228
+ const doFetch = o.fetch ?? globalThis.fetch;
229
+ if (typeof doFetch !== "function") throw new VerifyError("bad_options", "no fetch here: pass options.fetch");
230
+ let next = 1;
231
+ return async (method, params) => {
232
+ const id = next++;
233
+ let body;
234
+ try {
235
+ const res = await doFetch(url, {
236
+ method: "POST",
237
+ headers: { "content-type": "application/json" },
238
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
239
+ signal: o.signal
240
+ });
241
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
242
+ body = await res.json();
243
+ } catch (e) {
244
+ throw new VerifyError("rpc_error", `${method} failed: ${e instanceof Error ? e.message : String(e)}`);
245
+ }
246
+ if (!isObject(body)) throw new VerifyError("rpc_error", `${method}: the answer is not a JSON-RPC response`);
247
+ if (body.error !== void 0 && body.error !== null) {
248
+ const message = isObject(body.error) && typeof body.error.message === "string" ? body.error.message : JSON.stringify(body.error);
249
+ throw new VerifyError("rpc_error", `${method} failed: ${String(message).slice(0, 200)}`);
250
+ }
251
+ if (body.id !== id || !Object.prototype.hasOwnProperty.call(body, "result")) {
252
+ throw new VerifyError("rpc_error", `${method}: the answer is not a JSON-RPC response to this request`);
253
+ }
254
+ return body.result;
255
+ };
256
+ }
257
+ function quantity(v, method) {
258
+ if (typeof v !== "string" || !/^0x[0-9a-fA-F]{1,14}$/.test(v)) throw new VerifyError("rpc_error", `${method} did not answer a number`);
259
+ const n = BigInt(v);
260
+ if (n > BigInt(Number.MAX_SAFE_INTEGER)) throw new VerifyError("rpc_error", `${method} did not answer a number`);
261
+ return n;
262
+ }
263
+ function oneWord(v, what) {
264
+ if (typeof v !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(v)) {
265
+ throw new VerifyError("rpc_error", `${what} did not answer one 32-byte word (is the contract there at that block?)`);
266
+ }
267
+ return v.toLowerCase();
268
+ }
269
+ function parseExpectation(v) {
270
+ if (!isObject(v)) {
271
+ throw new VerifyError("bad_options", "options.expect is required: the order id, chain, asset, payout address and amount you sold for");
272
+ }
273
+ const e = {
274
+ orderId: bytes32(v.orderId, "expect.orderId", "bad_options"),
275
+ chainId: chain(v.chainId, "expect.chainId", "bad_options"),
276
+ token: address(v.token, "expect.token", "bad_options"),
277
+ payee: address(v.payee, "expect.payee", "bad_options"),
278
+ amount: uint256(v.amount, "expect.amount", "bad_options")
279
+ };
280
+ if (e.payee === NATIVE_TOKEN) throw new VerifyError("bad_options", "expect.payee is the zero address");
281
+ if (e.amount === 0n) throw new VerifyError("bad_options", "expect.amount is zero: pass what your payout address must receive");
282
+ return e;
283
+ }
284
+ function wholeNumber(v, what, code) {
285
+ if (typeof v !== "number" || !Number.isSafeInteger(v) || v < 1) throw new VerifyError(code, `${what} must be a whole number, 1 or more`);
286
+ return BigInt(v);
287
+ }
288
+ async function verifyPayment(verification, options) {
289
+ if (!isObject(options)) throw new VerifyError("bad_options", "options are required: expect, and rpcUrl or request");
290
+ const expect = parseExpectation(options.expect);
291
+ const floor = options.confirmations === void 0 ? 0n : wholeNumber(options.confirmations, "options.confirmations", "bad_options");
292
+ const rpc = transport(options);
293
+ const v = verification;
294
+ if (!isObject(v)) throw new VerifyError("bad_verification", "the verification block is missing (a test order has none)");
295
+ if (v.kind === "solana") {
296
+ throw new VerifyError("unsupported", "Solana orders are not supported yet: follow https://docs.chaindaddy.io/developer/verify-a-payment#solana-find-the-transfer");
297
+ }
298
+ if (v.kind !== "evm") throw new VerifyError("bad_verification", 'verification.kind is not "evm"');
299
+ if (typeof v.chainId !== "string" || !v.chainId.startsWith("eip155:")) throw new VerifyError("bad_verification", "verification.chainId is not an EVM chain id");
300
+ const chainId = chain(v.chainId, "verification.chainId", "bad_verification");
301
+ const checkout = address(v.checkout, "verification.checkout", "bad_verification");
302
+ const order = parseOrder(v.order, "bad_verification");
303
+ const asked = wholeNumber(v.confirmations, "verification.confirmations", "bad_verification");
304
+ let depth = BigInt(MIN_CONFIRMATIONS[chainId] ?? DEFAULT_MIN_CONFIRMATIONS);
305
+ if (asked > depth) depth = asked;
306
+ if (floor > depth) depth = floor;
307
+ if (chainId !== expect.chainId) throw new VerifyError("wrong_chain", `the order is on ${chainId}, not on ${expect.chainId}`);
308
+ if (!checkoutsOn(chainId).includes(checkout)) {
309
+ throw new VerifyError("unknown_checkout", `${checkout} is not a Chain Daddy checkout on ${chainId} that this version of @chaindaddy/js knows`);
310
+ }
311
+ if (order.orderId !== expect.orderId) throw new VerifyError("expectation_mismatch", `the block is for order ${order.orderId}, not ${expect.orderId}`);
312
+ if (order.token !== expect.token) throw new VerifyError("expectation_mismatch", `the order is paid in ${order.token}, not ${expect.token}`);
313
+ let toPayee = 0n;
314
+ for (let i = 0; i < order.payees.length; i++) {
315
+ if (order.payees[i] === expect.payee) toPayee += order.amounts[i];
316
+ }
317
+ if (toPayee < expect.amount) {
318
+ throw new VerifyError("expectation_mismatch", `the order sends ${expect.payee} ${toPayee}, less than the ${expect.amount} expected`);
319
+ }
320
+ const rpcChain = quantity(await rpc("eth_chainId", []), "eth_chainId");
321
+ if (`eip155:${rpcChain}` !== chainId) throw new VerifyError("wrong_chain", `the RPC endpoint is on eip155:${rpcChain}, the order on ${chainId}`);
322
+ const orderHash = oneWord(await rpc("eth_call", [{ to: checkout, data: encodeParsedOrder(order) }, "latest"]), "hashOrder");
323
+ const head = quantity(await rpc("eth_blockNumber", []), "eth_blockNumber");
324
+ const at = head - depth + 1n;
325
+ const result = { depth: Number(depth), checkout, orderHash, chainId };
326
+ if (at < 0n) return { paid: false, payer: null, blockNumber: 0, ...result };
327
+ const answer = oneWord(await rpc("eth_call", [{ to: checkout, data: encodePaidBy(orderHash) }, "0x" + at.toString(16)]), "paidBy");
328
+ if (!/^0x0{24}/.test(answer)) throw new VerifyError("rpc_error", "paidBy did not answer an address");
329
+ const payer = "0x" + answer.slice(26);
330
+ const paid = payer !== NATIVE_TOKEN;
331
+ return { paid, payer: paid ? payer : null, blockNumber: Number(at), ...result };
332
+ }
333
+ export {
334
+ CHECKOUTS,
335
+ MIN_CONFIRMATIONS,
336
+ NATIVE_TOKEN,
337
+ VerifyError,
338
+ encodeHashOrder,
339
+ encodePaidBy,
340
+ isKnownCheckout,
341
+ verifyPayment
342
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chaindaddy/js",
3
- "version": "0.1.0",
4
- "description": "Chain Daddy for any website: Sign in with Chain Daddy (a popup and a button), with embeds and checkout to follow",
3
+ "version": "0.3.0",
4
+ "description": "Chain Daddy for any website: Sign in with Chain Daddy (a popup and a button), a coin's apps on its own site (<cd-app>), checkout for a coin's store in a popup, and a server-side check that a store order was paid on chain",
5
5
  "homepage": "https://docs.chaindaddy.io/developer/javascript-sdk",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,9 +18,25 @@
18
18
  "types": "./dist/index.d.ts",
19
19
  "default": "./dist/index.js"
20
20
  },
21
+ "./api": {
22
+ "types": "./dist/api.d.ts",
23
+ "default": "./dist/api.js"
24
+ },
21
25
  "./signin": {
22
26
  "types": "./dist/signin.d.ts",
23
27
  "default": "./dist/signin.js"
28
+ },
29
+ "./embed": {
30
+ "types": "./dist/embed.d.ts",
31
+ "default": "./dist/embed.js"
32
+ },
33
+ "./checkout": {
34
+ "types": "./dist/checkout.d.ts",
35
+ "default": "./dist/checkout.js"
36
+ },
37
+ "./verify": {
38
+ "types": "./dist/verify.d.ts",
39
+ "default": "./dist/verify.js"
24
40
  }
25
41
  },
26
42
  "files": [
@@ -30,6 +46,7 @@
30
46
  "sideEffects": false,
31
47
  "scripts": {
32
48
  "build": "node scripts/build.mjs",
49
+ "gen:checkouts": "node scripts/gen-checkouts.mjs",
33
50
  "test": "vitest run"
34
51
  },
35
52
  "keywords": [