@chaindaddy/js 0.0.0-stage → 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.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * chain (CAIP-2, "eip155:8453") -> every ChainDaddyCheckout on it, lowercase.
3
+ * Frozen: this is the list verifyPayment checks against, and nothing in the
4
+ * process should be able to add to it.
5
+ */
6
+ export declare const CHECKOUTS: Readonly<Record<string, readonly string[]>>;
7
+ /**
8
+ * chain -> the confirmations Chain Daddy's own server waits on it before it
9
+ * calls an order confirmed. verifyPayment never reads shallower than this,
10
+ * whatever the block asks for: the block's number arrives from an API.
11
+ */
12
+ export declare const MIN_CONFIRMATIONS: Readonly<Record<string, number>>;
13
+ /** `token` of an order paid in the chain's own coin (ETH on Base, POL on Polygon). */
14
+ export declare const NATIVE_TOKEN = "0x0000000000000000000000000000000000000000";
15
+ export type VerifyErrorCode =
16
+ /** A Solana order: follow the manual recipe for now. */
17
+ 'unsupported'
18
+ /** What you passed (options, expect) is missing or malformed. */
19
+ | 'bad_options'
20
+ /** The verification block is malformed. */
21
+ | 'bad_verification'
22
+ /** The block names a contract that is not a Chain Daddy checkout on that chain. */
23
+ | 'unknown_checkout'
24
+ /** The block, your expectation and your RPC endpoint are not all the same chain. */
25
+ | 'wrong_chain'
26
+ /** The block is not the order, asset, payout address or amount you expect. */
27
+ | 'expectation_mismatch'
28
+ /** The RPC endpoint failed or answered something that is not an answer. */
29
+ | 'rpc_error';
30
+ /** Every failure of verifyPayment. A payment that is simply not there yet is not an error: it resolves `paid: false`. */
31
+ export declare class VerifyError extends Error {
32
+ readonly code: VerifyErrorCode;
33
+ constructor(code: VerifyErrorCode, message: string);
34
+ }
35
+ /** The Order struct the checkout contract takes, as the API prints it. */
36
+ export interface CheckoutOrder {
37
+ /** "0x" + 64 hex: the order's id. */
38
+ orderId: string;
39
+ /** The ERC-20 paid in, or NATIVE_TOKEN. */
40
+ token: string;
41
+ payees: readonly string[];
42
+ /** Decimal strings in the asset's smallest unit; amounts[i] goes to payees[i]. */
43
+ amounts: readonly string[];
44
+ /** Unix seconds. */
45
+ deadline: number | string | bigint;
46
+ }
47
+ /** An EVM order's `verification` block (GET /api/v2/iap/orders/{id}). */
48
+ export interface EvmVerification {
49
+ kind: 'evm';
50
+ /** CAIP-2, e.g. "eip155:8453". */
51
+ chainId: string;
52
+ checkout: string;
53
+ feeAddress?: string | null;
54
+ order: CheckoutOrder;
55
+ confirmations: number;
56
+ }
57
+ /** A Solana order's `verification` block. verifyPayment does not check these yet. */
58
+ export interface SolanaVerification {
59
+ kind: 'solana';
60
+ cluster: string;
61
+ reference: string;
62
+ memo: string;
63
+ }
64
+ export type Verification = EvmVerification | SolanaVerification;
65
+ /**
66
+ * What you sold, from your own records. Every field is required: the block
67
+ * arrives from an API, and these are what tie it to your sale.
68
+ */
69
+ export interface Expectation {
70
+ /** The id of the order you are checking. */
71
+ orderId: string;
72
+ /** The chain you sell on: "eip155:8453" or 8453. */
73
+ chainId: string | number;
74
+ /** The asset you are paid in: an ERC-20 address, or NATIVE_TOKEN for the chain's own coin. */
75
+ token: string;
76
+ /** Your payout address. */
77
+ payee: string;
78
+ /** The least the order must send your payout address, in the asset's smallest unit. A bigint or a decimal string, never a number. */
79
+ amount: bigint | string;
80
+ }
81
+ export interface VerifyOptions {
82
+ expect: Expectation;
83
+ /** A JSON-RPC endpoint for the order's chain. Yours, not Chain Daddy's. */
84
+ rpcUrl?: string;
85
+ /** Used with rpcUrl in place of the global fetch. */
86
+ fetch?: typeof fetch;
87
+ /** Instead of rpcUrl: an EIP-1193 style request function (a viem client's `request`, an ethers provider's `send` wrapped). */
88
+ request?: (args: {
89
+ method: string;
90
+ params: unknown[];
91
+ }) => Promise<unknown>;
92
+ /** A deeper wait than the block's `confirmations` and the chain's MIN_CONFIRMATIONS. A smaller number is ignored: this can only raise the depth. */
93
+ confirmations?: number;
94
+ /** Aborts the RPC requests made through rpcUrl. */
95
+ signal?: AbortSignal;
96
+ }
97
+ export interface PaymentResult {
98
+ /** True only when the contract says this exact order was paid, `depth` blocks deep. */
99
+ paid: boolean;
100
+ /** The wallet that paid, lowercase, or null. */
101
+ payer: string | null;
102
+ /** The confirmations asked for: the largest of the block's, yours and the chain's MIN_CONFIRMATIONS. */
103
+ depth: number;
104
+ /** The checkout contract that was asked, lowercase. */
105
+ checkout: string;
106
+ /** The block the answer was read at: head - depth + 1. */
107
+ blockNumber: number;
108
+ /** The order's EIP-712 hash on that contract. */
109
+ orderHash: string;
110
+ /** CAIP-2. */
111
+ chainId: string;
112
+ }
113
+ /** Calldata for hashOrder(order) on a checkout contract. */
114
+ export declare function encodeHashOrder(order: CheckoutOrder): string;
115
+ /** Calldata for paidBy(orderHash). The key is hashOrder's answer, not the order id. */
116
+ export declare function encodePaidBy(orderHash: string): string;
117
+ /** Whether `checkout` is a ChainDaddyCheckout this package knows on `chainId`. */
118
+ export declare function isKnownCheckout(chainId: string | number, checkout: string): boolean;
119
+ /**
120
+ * Checks on chain that an order was paid. Resolves `paid: true` only when the
121
+ * checkout contract itself says so, for exactly the order in `verification`,
122
+ * at least `depth` blocks ago. Rejects with a VerifyError when the block, your
123
+ * expectation or the RPC endpoint cannot be trusted to answer; never resolves
124
+ * `paid: true` on a doubt.
125
+ */
126
+ export declare function verifyPayment(verification: Verification, options: VerifyOptions): Promise<PaymentResult>;
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,6 +1,65 @@
1
1
  {
2
2
  "name": "@chaindaddy/js",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.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>), and a server-side check that a store order was paid on chain, with checkout to follow",
5
+ "homepage": "https://docs.chaindaddy.io/developer/javascript-sdk",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://gitlab.com/opencrown/chaindaddy-js.git",
9
+ "directory": "js"
10
+ },
11
+ "type": "module",
12
+ "publishConfig": {
13
+ "access": "public",
14
+ "tag": "beta"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./api": {
22
+ "types": "./dist/api.d.ts",
23
+ "default": "./dist/api.js"
24
+ },
25
+ "./signin": {
26
+ "types": "./dist/signin.d.ts",
27
+ "default": "./dist/signin.js"
28
+ },
29
+ "./embed": {
30
+ "types": "./dist/embed.d.ts",
31
+ "default": "./dist/embed.js"
32
+ },
33
+ "./verify": {
34
+ "types": "./dist/verify.d.ts",
35
+ "default": "./dist/verify.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md"
41
+ ],
42
+ "sideEffects": false,
43
+ "scripts": {
44
+ "build": "node scripts/build.mjs",
45
+ "gen:checkouts": "node scripts/gen-checkouts.mjs",
46
+ "test": "vitest run"
47
+ },
48
+ "keywords": [
49
+ "chaindaddy",
50
+ "oidc",
51
+ "sign-in",
52
+ "crypto",
53
+ "wallet"
54
+ ],
55
+ "license": "MIT",
56
+ "devDependencies": {
57
+ "esbuild": "^0.25.0",
58
+ "jsdom": "^26.0.0",
59
+ "typescript": "^5.7.0",
60
+ "vitest": "^3.2.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=20.0.0"
64
+ }
65
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.