@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/api.d.ts +5335 -0
- package/dist/api.js +0 -0
- package/dist/cd.js +14 -2
- package/dist/cd.js.sri +1 -1
- package/dist/checkout.d.ts +73 -0
- package/dist/checkout.js +168 -0
- package/dist/embed.d.ts +8 -0
- package/dist/embed.js +188 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +345 -1
- package/dist/verify.d.ts +126 -0
- package/dist/verify.js +342 -0
- package/package.json +19 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
|
-
var VERSION = true ? "0.
|
|
2
|
+
var VERSION = true ? "0.3.0" : "0.0.0";
|
|
3
3
|
function b64url(bytes) {
|
|
4
4
|
let s = "";
|
|
5
5
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -212,12 +212,356 @@ function defineSignInButton() {
|
|
|
212
212
|
CdSigninButton.observedAttributes = ["theme", "text"];
|
|
213
213
|
customElements.define("cd-signin-button", CdSigninButton);
|
|
214
214
|
}
|
|
215
|
+
|
|
216
|
+
// src/embed.ts
|
|
217
|
+
var PROD_ORIGIN = "https://embed.chaindaddy.io";
|
|
218
|
+
var STAGING_ORIGIN = "https://embed-staging.chaindaddy.io";
|
|
219
|
+
var READY_TIMEOUT_MS = 8e3;
|
|
220
|
+
var MAX_HEIGHT = 4e3;
|
|
221
|
+
var THEME_TOKENS = ["--cd-accent", "--cd-surface", "--cd-text", "--cd-text-muted", "--cd-border"];
|
|
222
|
+
function embedOriginFor(scriptSrc) {
|
|
223
|
+
try {
|
|
224
|
+
if (scriptSrc && new URL(scriptSrc).hostname === "js.staging.chaindaddy.io") return STAGING_ORIGIN;
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
return PROD_ORIGIN;
|
|
228
|
+
}
|
|
229
|
+
var STYLE2 = `
|
|
230
|
+
:host { display: block; }
|
|
231
|
+
iframe { display: block; width: 100%; border: 0; height: 1px; opacity: 0; color-scheme: normal; }
|
|
232
|
+
:host([data-cd-ready]) iframe { opacity: 1; }
|
|
233
|
+
:host([data-cd-ready]) slot { display: none; }
|
|
234
|
+
:host([data-cd-unavailable]) iframe { display: none; }
|
|
235
|
+
`;
|
|
236
|
+
var BUY_STYLE = `
|
|
237
|
+
:host { display: inline-block; }
|
|
238
|
+
button { box-sizing: border-box; min-height: 40px; padding: 0 16px; border-radius: 10px; cursor: pointer;
|
|
239
|
+
font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
240
|
+
border: 1px solid var(--cd-border, #d0d5dd); background: var(--cd-accent, #f59e0b); color: var(--cd-text, #101828); }
|
|
241
|
+
button:focus-visible { outline: 2px solid var(--cd-accent, #f59e0b); outline-offset: 2px; }
|
|
242
|
+
`;
|
|
243
|
+
function defineEmbedElements(options = {}) {
|
|
244
|
+
try {
|
|
245
|
+
if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") return;
|
|
246
|
+
const origin = options.origin === STAGING_ORIGIN ? STAGING_ORIGIN : PROD_ORIGIN;
|
|
247
|
+
class CdFrame extends HTMLElement {
|
|
248
|
+
constructor() {
|
|
249
|
+
super(...arguments);
|
|
250
|
+
this.kind = "app";
|
|
251
|
+
this.frame = null;
|
|
252
|
+
this.onMessage = (event) => this.receive(event);
|
|
253
|
+
}
|
|
254
|
+
connectedCallback() {
|
|
255
|
+
try {
|
|
256
|
+
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
257
|
+
this.mount();
|
|
258
|
+
} catch {
|
|
259
|
+
this.unavailable();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
disconnectedCallback() {
|
|
263
|
+
try {
|
|
264
|
+
window.removeEventListener("message", this.onMessage);
|
|
265
|
+
clearTimeout(this.timer);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
attributeChangedCallback() {
|
|
270
|
+
try {
|
|
271
|
+
if (this.isConnected && this.shadowRoot) this.mount();
|
|
272
|
+
} catch {
|
|
273
|
+
this.unavailable();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** The frame's address, or null when the element does not name enough. */
|
|
277
|
+
src() {
|
|
278
|
+
const crown = (this.getAttribute("crown") ?? "").trim();
|
|
279
|
+
const chain = (this.getAttribute("chain") ?? "").trim();
|
|
280
|
+
const app = (this.getAttribute("app") ?? "").trim();
|
|
281
|
+
if (!/^[0-9]{1,18}$/.test(crown) || !chain || this.kind === "app" && !app) return null;
|
|
282
|
+
const q = new URLSearchParams({ crown, chain });
|
|
283
|
+
if (this.kind === "app") q.set("app", app);
|
|
284
|
+
else q.set("kind", "coin");
|
|
285
|
+
q.set("mode", this.mode());
|
|
286
|
+
q.set("v", "1");
|
|
287
|
+
return `${origin}/v1/frame?${q.toString()}`;
|
|
288
|
+
}
|
|
289
|
+
mode() {
|
|
290
|
+
const attr = this.getAttribute("mode");
|
|
291
|
+
if (attr === "light" || attr === "dark") return attr;
|
|
292
|
+
try {
|
|
293
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
294
|
+
} catch {
|
|
295
|
+
return "light";
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
mount() {
|
|
299
|
+
const root = this.shadowRoot;
|
|
300
|
+
window.removeEventListener("message", this.onMessage);
|
|
301
|
+
clearTimeout(this.timer);
|
|
302
|
+
this.removeAttribute("data-cd-ready");
|
|
303
|
+
this.removeAttribute("data-cd-unavailable");
|
|
304
|
+
const src = this.src();
|
|
305
|
+
if (!src) {
|
|
306
|
+
root.innerHTML = "<slot></slot>";
|
|
307
|
+
this.frame = null;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
root.innerHTML = `<style>${STYLE2}</style><slot></slot>`;
|
|
311
|
+
const frame = document.createElement("iframe");
|
|
312
|
+
frame.setAttribute("loading", "lazy");
|
|
313
|
+
frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
314
|
+
frame.setAttribute("allow", "clipboard-write; fullscreen");
|
|
315
|
+
frame.setAttribute("title", this.kind === "coin" ? "Coin on Chain Daddy" : "App on Chain Daddy");
|
|
316
|
+
frame.src = src;
|
|
317
|
+
root.appendChild(frame);
|
|
318
|
+
this.frame = frame;
|
|
319
|
+
window.addEventListener("message", this.onMessage);
|
|
320
|
+
this.timer = setTimeout(() => {
|
|
321
|
+
if (!this.hasAttribute("data-cd-ready")) this.unavailable();
|
|
322
|
+
}, READY_TIMEOUT_MS);
|
|
323
|
+
}
|
|
324
|
+
receive(event) {
|
|
325
|
+
try {
|
|
326
|
+
if (event.origin !== origin || !this.frame || !event.source || event.source !== this.frame.contentWindow) return;
|
|
327
|
+
const data = event.data;
|
|
328
|
+
if (!data || typeof data !== "object") return;
|
|
329
|
+
if (data.type === "cd:ready") {
|
|
330
|
+
clearTimeout(this.timer);
|
|
331
|
+
this.removeAttribute("data-cd-unavailable");
|
|
332
|
+
this.setAttribute("data-cd-ready", "");
|
|
333
|
+
this.sendTheme();
|
|
334
|
+
this.dispatchEvent(new CustomEvent("cd:ready"));
|
|
335
|
+
} else if (data.type === "cd:resize") {
|
|
336
|
+
const h = Number(data.height);
|
|
337
|
+
if (Number.isFinite(h)) this.frame.style.height = `${Math.max(1, Math.min(MAX_HEIGHT, Math.ceil(h)))}px`;
|
|
338
|
+
} else if (data.type === "cd:unavailable") {
|
|
339
|
+
this.unavailable();
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** The page's --cd-* colours, for an app that takes them. */
|
|
345
|
+
sendTheme() {
|
|
346
|
+
const tokens = {};
|
|
347
|
+
const style = getComputedStyle(this);
|
|
348
|
+
for (const token of THEME_TOKENS) {
|
|
349
|
+
const value = style.getPropertyValue(token).trim();
|
|
350
|
+
if (value) tokens[token] = value;
|
|
351
|
+
}
|
|
352
|
+
this.frame?.contentWindow?.postMessage({ type: "cd:theme", tokens }, origin);
|
|
353
|
+
}
|
|
354
|
+
unavailable() {
|
|
355
|
+
try {
|
|
356
|
+
clearTimeout(this.timer);
|
|
357
|
+
this.removeAttribute("data-cd-ready");
|
|
358
|
+
if (this.hasAttribute("data-cd-unavailable")) return;
|
|
359
|
+
this.setAttribute("data-cd-unavailable", "");
|
|
360
|
+
this.dispatchEvent(new CustomEvent("cd:unavailable"));
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
CdFrame.observedAttributes = ["crown", "chain", "app", "mode"];
|
|
366
|
+
class CdApp extends CdFrame {
|
|
367
|
+
}
|
|
368
|
+
class CdCoin extends CdFrame {
|
|
369
|
+
constructor() {
|
|
370
|
+
super(...arguments);
|
|
371
|
+
this.kind = "coin";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
class CdBuy extends HTMLElement {
|
|
375
|
+
connectedCallback() {
|
|
376
|
+
try {
|
|
377
|
+
const link = this.querySelector("a[href]");
|
|
378
|
+
const href = link ? new URL(link.getAttribute("href") ?? "", document.baseURI) : null;
|
|
379
|
+
if (!href || href.protocol !== "https:") return;
|
|
380
|
+
const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
|
|
381
|
+
root.innerHTML = `<style>${BUY_STYLE}</style><button type="button" part="button"></button>`;
|
|
382
|
+
const button = root.querySelector("button");
|
|
383
|
+
button.textContent = (link.textContent ?? "").trim() || "Buy on Chain Daddy";
|
|
384
|
+
button.addEventListener("click", () => {
|
|
385
|
+
try {
|
|
386
|
+
if (!window.open(href.toString(), "cd-buy", "popup,width=460,height=760")) window.location.assign(href.toString());
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
for (const [name, ctor] of [["cd-app", CdApp], ["cd-coin", CdCoin], ["cd-buy", CdBuy]]) {
|
|
395
|
+
if (!customElements.get(name)) customElements.define(name, ctor);
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/checkout.ts
|
|
402
|
+
var ORIGINS = ["https://chaindaddy.io", "https://staging.chaindaddy.io"];
|
|
403
|
+
var windowName = () => "cd-checkout-" + randomToken();
|
|
404
|
+
var FEATURES = "popup,width=480,height=820";
|
|
405
|
+
var TICK_MS = 300;
|
|
406
|
+
var QUIET_TICKS = 7;
|
|
407
|
+
function parseCheckoutUrl(url) {
|
|
408
|
+
if (typeof url !== "string") return null;
|
|
409
|
+
let u;
|
|
410
|
+
try {
|
|
411
|
+
u = new URL(url);
|
|
412
|
+
} catch {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
const m = /^\/_pay\/([\w-]+)$/.exec(u.pathname);
|
|
416
|
+
return m && ORIGINS.includes(u.origin) && !u.username && !u.password ? { href: u.origin + u.pathname, origin: u.origin, id: m[1] } : null;
|
|
417
|
+
}
|
|
418
|
+
function successTarget(successUrl, id) {
|
|
419
|
+
if (typeof successUrl !== "string") return null;
|
|
420
|
+
let u;
|
|
421
|
+
try {
|
|
422
|
+
u = new URL(successUrl, location.href);
|
|
423
|
+
} catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
if (!/^https?:$/.test(u.protocol) || u.username || u.password) return null;
|
|
427
|
+
const key = (pair) => {
|
|
428
|
+
const raw = pair.split("=")[0];
|
|
429
|
+
try {
|
|
430
|
+
return decodeURIComponent(raw.replace(/\+/g, " "));
|
|
431
|
+
} catch {
|
|
432
|
+
return raw;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const pairs = u.search.slice(1).split("&").filter((pair) => pair && key(pair) !== "session_id");
|
|
436
|
+
pairs.push("session_id=" + encodeURIComponent(id));
|
|
437
|
+
return u.origin + u.pathname + "?" + pairs.join("&") + u.hash;
|
|
438
|
+
}
|
|
439
|
+
var current;
|
|
440
|
+
function wait(popup, target, success) {
|
|
441
|
+
return new Promise((resolve) => {
|
|
442
|
+
let ready = false;
|
|
443
|
+
let quiet = 0;
|
|
444
|
+
let last;
|
|
445
|
+
const done = (result2) => {
|
|
446
|
+
clearInterval(timer);
|
|
447
|
+
clearTimeout(last);
|
|
448
|
+
window.removeEventListener("message", onMessage);
|
|
449
|
+
if (success && result2.status === "completed") location.assign(success);
|
|
450
|
+
resolve(result2);
|
|
451
|
+
};
|
|
452
|
+
const onMessage = (e) => {
|
|
453
|
+
if (e.origin !== target.origin || e.source !== popup) return;
|
|
454
|
+
const data = e.data;
|
|
455
|
+
if (!data || typeof data !== "object") return;
|
|
456
|
+
if (data.type === "cd:checkout.ready") ready = true;
|
|
457
|
+
const s = data.session;
|
|
458
|
+
if (!s || typeof s !== "object" || s.id !== target.id) return;
|
|
459
|
+
if (data.type === "cd:checkout.cancelled") done({ status: "cancelled", session: { id: target.id } });
|
|
460
|
+
else if (data.type === "cd:checkout.completed" && (s.status === "complete" || s.status === "paid")) {
|
|
461
|
+
const session = { id: target.id, status: s.status };
|
|
462
|
+
if (typeof s.orderId === "string" && /^[\w-]{1,128}$/.test(s.orderId)) session.orderId = s.orderId;
|
|
463
|
+
done({ status: "completed", session });
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const hello = () => {
|
|
467
|
+
try {
|
|
468
|
+
popup.postMessage({ type: "cd:checkout.hello" }, target.origin);
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
const timer = setInterval(() => {
|
|
473
|
+
if (popup.closed) {
|
|
474
|
+
clearInterval(timer);
|
|
475
|
+
last = setTimeout(() => done({ status: "closed" }), TICK_MS);
|
|
476
|
+
} else if (!ready || ++quiet % QUIET_TICKS === 0) hello();
|
|
477
|
+
}, TICK_MS);
|
|
478
|
+
window.addEventListener("message", onMessage);
|
|
479
|
+
hello();
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
function checkout(o) {
|
|
483
|
+
const refused = () => new Error("checkout: url is not a Chain Daddy checkout session link");
|
|
484
|
+
const track = (popup, result2, href) => {
|
|
485
|
+
current = { popup, result: result2, href };
|
|
486
|
+
const over = () => {
|
|
487
|
+
if (current && current.popup === popup) current = void 0;
|
|
488
|
+
};
|
|
489
|
+
result2.then(over, over);
|
|
490
|
+
return result2;
|
|
491
|
+
};
|
|
492
|
+
const byLink = (target) => {
|
|
493
|
+
location.assign(target.href);
|
|
494
|
+
return { status: "redirected" };
|
|
495
|
+
};
|
|
496
|
+
try {
|
|
497
|
+
const { url, successUrl } = o;
|
|
498
|
+
if (successUrl && !successTarget(successUrl, "")) throw new Error("checkout: successUrl is not a web address");
|
|
499
|
+
const success = (t) => successUrl ? successTarget(successUrl, t.id) : null;
|
|
500
|
+
const target = typeof url === "function" ? null : parseCheckoutUrl(url);
|
|
501
|
+
if (typeof url !== "function" && !target) throw refused();
|
|
502
|
+
if (current && !current.popup.closed && (!target || current.href === target.href)) {
|
|
503
|
+
current.popup.focus();
|
|
504
|
+
return current.result;
|
|
505
|
+
}
|
|
506
|
+
if (target) {
|
|
507
|
+
const popup2 = window.open(target.href, windowName(), FEATURES);
|
|
508
|
+
return popup2 ? track(popup2, wait(popup2, target, success(target)), target.href) : Promise.resolve(byLink(target));
|
|
509
|
+
}
|
|
510
|
+
const popup = window.open("", windowName(), FEATURES);
|
|
511
|
+
const result2 = new Promise((resolve) => resolve(url())).then(
|
|
512
|
+
(fetched) => {
|
|
513
|
+
const t = parseCheckoutUrl(fetched);
|
|
514
|
+
if (!t) {
|
|
515
|
+
popup?.close();
|
|
516
|
+
throw refused();
|
|
517
|
+
}
|
|
518
|
+
if (!popup) return byLink(t);
|
|
519
|
+
popup.location.href = t.href;
|
|
520
|
+
return wait(popup, t, success(t));
|
|
521
|
+
},
|
|
522
|
+
(e) => {
|
|
523
|
+
popup?.close();
|
|
524
|
+
throw e;
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
return popup ? track(popup, result2) : result2;
|
|
528
|
+
} catch (e) {
|
|
529
|
+
return Promise.reject(e);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
var bound = false;
|
|
533
|
+
var reporting;
|
|
534
|
+
function bindCheckoutButtons() {
|
|
535
|
+
if (bound || typeof document === "undefined") return;
|
|
536
|
+
bound = true;
|
|
537
|
+
document.addEventListener("click", (e) => {
|
|
538
|
+
const el = e.target instanceof Element ? e.target.closest("[data-cd-checkout]") : null;
|
|
539
|
+
if (!el || e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
540
|
+
const url = el.getAttribute("data-cd-checkout") || el.href;
|
|
541
|
+
const successUrl = el.getAttribute("data-cd-success");
|
|
542
|
+
if (!parseCheckoutUrl(url) || successUrl && !successTarget(successUrl, "")) return;
|
|
543
|
+
e.preventDefault();
|
|
544
|
+
const visit = checkout({ url, successUrl });
|
|
545
|
+
if (visit === reporting) return;
|
|
546
|
+
reporting = visit;
|
|
547
|
+
visit.then(
|
|
548
|
+
(detail) => void el.dispatchEvent(new CustomEvent("cd:checkout", { detail, bubbles: true })),
|
|
549
|
+
() => {
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
215
554
|
export {
|
|
216
555
|
DEFAULT_ISSUER,
|
|
217
556
|
VERSION,
|
|
557
|
+
bindCheckoutButtons,
|
|
218
558
|
challengeFor,
|
|
559
|
+
checkout,
|
|
560
|
+
defineEmbedElements,
|
|
219
561
|
defineSignInButton,
|
|
562
|
+
embedOriginFor,
|
|
220
563
|
exchangeCode,
|
|
221
564
|
handleCallback,
|
|
565
|
+
parseCheckoutUrl,
|
|
222
566
|
signIn
|
|
223
567
|
};
|
package/dist/verify.d.ts
ADDED
|
@@ -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>;
|