@cardano-slips/flow 0.2.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.
Files changed (73) hide show
  1. package/README.md +53 -9
  2. package/dist/address.d.ts +12 -0
  3. package/dist/address.d.ts.map +1 -1
  4. package/dist/address.js +43 -0
  5. package/dist/address.js.map +1 -1
  6. package/dist/card.css +492 -0
  7. package/dist/card.d.ts +41 -0
  8. package/dist/card.d.ts.map +1 -0
  9. package/dist/card.js +69 -0
  10. package/dist/card.js.map +1 -0
  11. package/dist/complete-error.d.ts +8 -1
  12. package/dist/complete-error.d.ts.map +1 -1
  13. package/dist/complete-error.js +6 -1
  14. package/dist/complete-error.js.map +1 -1
  15. package/dist/complete.d.ts +29 -10
  16. package/dist/complete.d.ts.map +1 -1
  17. package/dist/complete.js +83 -9
  18. package/dist/complete.js.map +1 -1
  19. package/dist/derived.d.ts +8 -0
  20. package/dist/derived.d.ts.map +1 -0
  21. package/dist/derived.js +2 -0
  22. package/dist/derived.js.map +1 -0
  23. package/dist/effects-panel.d.ts +37 -0
  24. package/dist/effects-panel.d.ts.map +1 -0
  25. package/dist/effects-panel.js +64 -0
  26. package/dist/effects-panel.js.map +1 -0
  27. package/dist/effects.css +629 -0
  28. package/dist/exchange.d.ts +54 -0
  29. package/dist/exchange.d.ts.map +1 -0
  30. package/dist/exchange.js +136 -0
  31. package/dist/exchange.js.map +1 -0
  32. package/dist/explain.d.ts +39 -0
  33. package/dist/explain.d.ts.map +1 -0
  34. package/dist/explain.js +172 -0
  35. package/dist/explain.js.map +1 -0
  36. package/dist/index.d.ts +19 -3
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +9 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/ledger.d.ts +74 -0
  41. package/dist/ledger.d.ts.map +1 -0
  42. package/dist/ledger.js +402 -0
  43. package/dist/ledger.js.map +1 -0
  44. package/dist/outcome-panel.d.ts +63 -0
  45. package/dist/outcome-panel.d.ts.map +1 -0
  46. package/dist/outcome-panel.js +40 -0
  47. package/dist/outcome-panel.js.map +1 -0
  48. package/dist/parameter-form.d.ts +26 -0
  49. package/dist/parameter-form.d.ts.map +1 -0
  50. package/dist/parameter-form.js +42 -0
  51. package/dist/parameter-form.js.map +1 -0
  52. package/dist/slip-card.d.ts +55 -0
  53. package/dist/slip-card.d.ts.map +1 -0
  54. package/dist/slip-card.js +89 -0
  55. package/dist/slip-card.js.map +1 -0
  56. package/dist/tokens.css +57 -32
  57. package/package.json +9 -5
  58. package/src/address.ts +53 -0
  59. package/src/card.css +492 -0
  60. package/src/card.ts +86 -0
  61. package/src/complete-error.ts +14 -2
  62. package/src/complete.ts +128 -19
  63. package/src/derived.ts +8 -0
  64. package/src/effects-panel.tsx +386 -0
  65. package/src/effects.css +629 -0
  66. package/src/exchange.ts +212 -0
  67. package/src/explain.ts +211 -0
  68. package/src/index.ts +39 -3
  69. package/src/ledger.ts +503 -0
  70. package/src/outcome-panel.tsx +263 -0
  71. package/src/parameter-form.tsx +138 -0
  72. package/src/slip-card.tsx +244 -0
  73. package/src/tokens.css +57 -32
@@ -0,0 +1,212 @@
1
+ /**
2
+ * The two HTTP exchanges a client makes: `GET` for the Slip and `POST` for the
3
+ * partial intent. Every way either can fail arrives as one error in the spec's
4
+ * vocabulary, classified by the rules in "Failure responses".
5
+ */
6
+ import {
7
+ type BoundedAnswer,
8
+ boundedRequest,
9
+ type BoundedRequestOptions,
10
+ checkTemplates,
11
+ classifyErrorCode,
12
+ classifyStatus,
13
+ decodePartialIntent,
14
+ decodeSlip,
15
+ decodeSlipError,
16
+ fetchDomainMapping,
17
+ type InsecureSlipUrl,
18
+ type Network,
19
+ type PartialIntent,
20
+ PROTOCOL_VERSION,
21
+ resolveSlipUrl,
22
+ type Slip,
23
+ type SlipErrorClass
24
+ } from "@cardano-slips/core"
25
+ import { Data, Effect, Either } from "effect"
26
+
27
+ export type ExchangeOptions = BoundedRequestOptions
28
+
29
+ /**
30
+ * `code` is a spec code, or one an endpoint sent that the spec does not define.
31
+ * It is absent only where the body could not be read, and then `status` is all
32
+ * there is to go on.
33
+ */
34
+ export class ExchangeError extends Data.TaggedError("ExchangeError")<{
35
+ readonly code?: string
36
+ readonly errorClass: SlipErrorClass
37
+ /** The endpoint's own words, present only when its failure body was readable. */
38
+ readonly endpointMessage?: string
39
+ readonly field?: string
40
+ readonly status?: number
41
+ /** Seconds, from `Retry-After`. */
42
+ readonly retryAfter?: number
43
+ /** For whoever integrates this; never put in front of a person. */
44
+ readonly detail: string
45
+ }> {
46
+ override get message(): string {
47
+ return this.detail
48
+ }
49
+ }
50
+
51
+ const clientFailure = (code: string, detail: string, status?: number): ExchangeError =>
52
+ new ExchangeError({
53
+ code,
54
+ errorClass: classifyErrorCode(code),
55
+ detail,
56
+ ...(status === undefined ? {} : { status })
57
+ })
58
+
59
+ /** Whole seconds or an HTTP date, per RFC 9110; anything else says nothing. */
60
+ const readRetryAfter = (header: string | null, now: number): number | undefined => {
61
+ if (header === null) return undefined
62
+ const trimmed = header.trim()
63
+ if (/^\d+$/.test(trimmed)) return Number(trimmed)
64
+ const at = Date.parse(trimmed)
65
+ return Number.isNaN(at) ? undefined : Math.max(0, Math.ceil((at - now) / 1000))
66
+ }
67
+
68
+ const parseJson = (text: string): unknown => {
69
+ try {
70
+ return JSON.parse(text) as unknown
71
+ } catch {
72
+ return undefined
73
+ }
74
+ }
75
+
76
+ /** A major version is checked before the shape, so a newer protocol is not reported as a broken one. */
77
+ const newerVersion = (body: unknown): string | undefined => {
78
+ if (typeof body !== "object" || body === null) return undefined
79
+ const version = (body as Record<string, unknown>).version
80
+ return typeof version === "string" && /^[1-9][0-9]*$/.test(version) && version !== PROTOCOL_VERSION
81
+ ? version
82
+ : undefined
83
+ }
84
+
85
+ const failureFrom = (response: Response, text: string): ExchangeError => {
86
+ const status = response.status
87
+ const retryAfter = readRetryAfter(response.headers.get("retry-after"), Date.now())
88
+ const retry = retryAfter === undefined ? {} : { retryAfter }
89
+ const body = parseJson(text)
90
+
91
+ const version = newerVersion(body)
92
+ if (version !== undefined) {
93
+ return clientFailure("UNSUPPORTED_VERSION", `the failure body speaks version ${version}`, status)
94
+ }
95
+
96
+ const read = decodeSlipError(body)
97
+ // Unreadable, so classified by status and never rendered: it is as likely an
98
+ // intermediary's error page as the publisher's words.
99
+ if (Either.isLeft(read)) {
100
+ return new ExchangeError({
101
+ errorClass: classifyStatus(status),
102
+ status,
103
+ detail: `answered ${status} with a body that is not a failure response`,
104
+ ...retry
105
+ })
106
+ }
107
+
108
+ return new ExchangeError({
109
+ code: read.right.code,
110
+ errorClass: classifyErrorCode(read.right.code),
111
+ endpointMessage: read.right.message,
112
+ status,
113
+ detail: `answered ${status} ${read.right.code}`,
114
+ ...(read.right.field === undefined ? {} : { field: read.right.field }),
115
+ ...retry
116
+ })
117
+ }
118
+
119
+ type Answer = BoundedAnswer
120
+
121
+ /**
122
+ * One request, through core's bounded request. Anything that stops a usable
123
+ * answer arriving — DNS, TLS, a refused CORS read, the clock, the size — is `UNREACHABLE`.
124
+ */
125
+ const exchange = (url: string, init: RequestInit, options: ExchangeOptions): Effect.Effect<Answer, ExchangeError> =>
126
+ boundedRequest(url, init, options).pipe(Effect.mapError((failure) => clientFailure(failure.code, failure.detail)))
127
+
128
+ const successBody = ({ response, text }: Answer): Effect.Effect<unknown, ExchangeError> => {
129
+ if (response.status !== 200) return Effect.fail(failureFrom(response, text))
130
+ const body = parseJson(text)
131
+ if (body === undefined) {
132
+ return Effect.fail(clientFailure("MALFORMED_RESPONSE", `${response.url} answered 200 with something not JSON`))
133
+ }
134
+ const version = newerVersion(body)
135
+ if (version !== undefined) {
136
+ return Effect.fail(clientFailure("UNSUPPORTED_VERSION", `the response speaks version ${version}`))
137
+ }
138
+ return Effect.succeed(body)
139
+ }
140
+
141
+ export type FetchedSlip = {
142
+ readonly slip: Slip
143
+ /** The endpoint the link resolved to. Every `href` resolves against it. */
144
+ readonly discoveryUrl: string
145
+ }
146
+
147
+ /**
148
+ * The link to a Slip a person can be shown, or why not. `slips.json` is read
149
+ * first, because the link a person shares is not always the endpoint. A link
150
+ * that is not `https:` fails as `InsecureSlipUrl`, before anything is fetched.
151
+ */
152
+ export const fetchSlip = (
153
+ link: string,
154
+ options: ExchangeOptions = {}
155
+ ): Effect.Effect<FetchedSlip, ExchangeError | InsecureSlipUrl> =>
156
+ Effect.gen(function* () {
157
+ const mapping = yield* fetchDomainMapping(link, options).pipe(
158
+ Effect.catchTag("DomainMappingFailure", (failure) => Effect.fail(clientFailure(failure.code, failure.detail)))
159
+ )
160
+ const discoveryUrl = yield* resolveSlipUrl(link, mapping)
161
+
162
+ const answer = yield* exchange(discoveryUrl, { method: "GET", headers: { accept: "application/json" } }, options)
163
+ const body = yield* successBody(answer)
164
+
165
+ const slip = decodeSlip(body)
166
+ if (Either.isLeft(slip)) {
167
+ return yield* Effect.fail(clientFailure("MALFORMED_RESPONSE", `${discoveryUrl} did not answer with a Slip`))
168
+ }
169
+
170
+ const defects = checkTemplates(slip.right, discoveryUrl)
171
+ if (defects.length > 0) {
172
+ return yield* Effect.fail(
173
+ clientFailure("MALFORMED_RESPONSE", `${discoveryUrl} answered a Slip with ${defects[0]?._tag ?? "a defect"}`)
174
+ )
175
+ }
176
+
177
+ return { slip: slip.right, discoveryUrl }
178
+ })
179
+
180
+ export type IntentRequest = {
181
+ /** Absolute, as `SlipCard` hands it over. */
182
+ readonly href: string
183
+ readonly changeAddress: string
184
+ readonly network: Network
185
+ }
186
+
187
+ /**
188
+ * The partial intent for one action. The body is the change address and the
189
+ * network and nothing else: Mode A never tells an endpoint what a wallet holds.
190
+ */
191
+ export const requestIntent = (
192
+ { changeAddress, href, network }: IntentRequest,
193
+ options: ExchangeOptions = {}
194
+ ): Effect.Effect<PartialIntent, ExchangeError> =>
195
+ Effect.gen(function* () {
196
+ const answer = yield* exchange(
197
+ href,
198
+ {
199
+ method: "POST",
200
+ headers: { accept: "application/json", "content-type": "application/json" },
201
+ body: JSON.stringify({ changeAddress, network })
202
+ },
203
+ options
204
+ )
205
+ const body = yield* successBody(answer)
206
+
207
+ const intent = decodePartialIntent(body)
208
+ if (Either.isLeft(intent)) {
209
+ return yield* Effect.fail(clientFailure("MALFORMED_RESPONSE", `${href} did not answer with a partial intent`))
210
+ }
211
+ return intent.right
212
+ })
package/src/explain.ts ADDED
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The words the effects panel puts in front of a person: exact figures, and a
3
+ * plain account of each mismatch. Nothing here rounds, and nothing guesses a
4
+ * token's decimals — the CIP requires base units where they are not known.
5
+ */
6
+ import type { Reason, UnsupportedMember } from "@cardano-slips/verifier"
7
+
8
+ const lovelacePerAda = 1_000_000n
9
+
10
+ const grouped = (digits: string): string => digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
11
+
12
+ const magnitude = (value: bigint): bigint => (value < 0n ? -value : value)
13
+
14
+ /** Lovelace as ADA, to the lovelace. The sign is dropped: "you pay" and "you receive" carry the direction. */
15
+ export const formatAda = (lovelace: bigint): string => {
16
+ const amount = magnitude(lovelace)
17
+ const whole = grouped((amount / lovelacePerAda).toString())
18
+ const fraction = (amount % lovelacePerAda).toString().padStart(6, "0").replace(/0+$/, "")
19
+ return `${fraction === "" ? whole : `${whole}.${fraction}`} ADA`
20
+ }
21
+
22
+ /** A token quantity in base units, unsigned for the same reason as `formatAda`. */
23
+ export const formatQuantity = (quantity: bigint): string => grouped(magnitude(quantity).toString())
24
+
25
+ const bytesOf = (hex: string): Uint8Array =>
26
+ Uint8Array.from({ length: hex.length / 2 }, (_, index) => Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16))
27
+
28
+ /** The asset name as text, or null where it is not text a person could read — which is then no name at all. */
29
+ export const assetLabel = (assetName: string): string | null => {
30
+ if (assetName === "") return null
31
+ try {
32
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytesOf(assetName))
33
+ return /\p{C}/u.test(text) ? null : text
34
+ } catch {
35
+ return null
36
+ }
37
+ }
38
+
39
+ const tokenAmount = (quantity: bigint, policyId: string, assetName: string): string => {
40
+ const label = assetLabel(assetName)
41
+ return label === null
42
+ ? `${formatQuantity(quantity)} of token ${assetName || "with no name"} under policy ${policyId}`
43
+ : `${formatQuantity(quantity)} ${label}`
44
+ }
45
+
46
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
47
+
48
+ /** Unix milliseconds as a UTC instant. UTC so that the publisher and the person read the same moment. */
49
+ export const formatInstant = (time: bigint): string => {
50
+ const date = new Date(Number(time))
51
+ const two = (value: number): string => value.toString().padStart(2, "0")
52
+ return `${date.getUTCDate()} ${months[date.getUTCMonth()]} ${date.getUTCFullYear()}, ${two(date.getUTCHours())}:${two(date.getUTCMinutes())} UTC`
53
+ }
54
+
55
+ /** Whole seconds until `until`, never below zero. */
56
+ export const secondsLeft = (until: bigint, now: bigint): number =>
57
+ until <= now ? 0 : Math.floor(Number(until - now) / 1000)
58
+
59
+ /** The sheet's clock: `4m 12s`, and hours in front once there are any. */
60
+ export const formatCountdown = (seconds: number): string => {
61
+ const hours = Math.floor(seconds / 3600)
62
+ const minutes = Math.floor((seconds % 3600) / 60)
63
+ const rest = `${(seconds % 60).toString().padStart(2, "0")}s`
64
+ return hours > 0 ? `${hours}h ${minutes.toString().padStart(2, "0")}m ${rest}` : `${minutes}m ${rest}`
65
+ }
66
+
67
+ /**
68
+ * A bech32 string cut to what a column can hold: the prefix, three characters,
69
+ * and the last four. Only where the full string is a click away.
70
+ */
71
+ export const shortened = (text: string): string => {
72
+ const separator = text.lastIndexOf("1")
73
+ if (separator < 1 || text.length - separator <= 12) return text
74
+ return `${text.slice(0, separator + 4)}…${text.slice(-4)}`
75
+ }
76
+
77
+ const unsupportedWords: { readonly [member in UnsupportedMember]: string } = {
78
+ "reference-input": "reads another output without spending it",
79
+ collateral: "puts up collateral",
80
+ "required-signer": "asks for an extra signature",
81
+ vote: "casts a governance vote",
82
+ proposal: "submits a governance proposal",
83
+ donation: "donates to the treasury",
84
+ "treasury-value": "states the treasury's value",
85
+ script: "runs a script",
86
+ datum: "attaches data to an output"
87
+ }
88
+
89
+ const payments = (count: number): string => (count === 1 ? "1 payment" : `${count} payments`)
90
+
91
+ const certificates = (count: number): string => (count === 1 ? "1 certificate" : `${count} certificates`)
92
+
93
+ /**
94
+ * What the block shows for one reason, in the sheet's voice: the transaction
95
+ * is held to "the link", and a sentence never carries an address — `where` does,
96
+ * in full, beside it. The CIP asks for the difference, not the rule: `declared`
97
+ * is what the link asked for and `actual` is what the transaction does, where
98
+ * the reason has a figure for each.
99
+ */
100
+ export type Explanation = {
101
+ readonly summary: string
102
+ readonly declared?: string
103
+ readonly actual?: string
104
+ /** The address or reward account the reason is about. */
105
+ readonly where?: string
106
+ }
107
+
108
+ export const explainReason = (reason: Reason): Explanation => {
109
+ switch (reason.code) {
110
+ case "output.missing":
111
+ return {
112
+ summary: "The transaction leaves out a payment the link asks for.",
113
+ declared: payments(reason.declared),
114
+ actual: payments(reason.paid),
115
+ where: reason.address
116
+ }
117
+ case "output.undeclared":
118
+ return reason.declared === 0
119
+ ? { summary: "The transaction pays an address the link never mentions.", where: reason.address }
120
+ : {
121
+ summary: "The transaction pays one address more times than the link asks for.",
122
+ declared: payments(reason.declared),
123
+ actual: payments(reason.paid),
124
+ where: reason.address
125
+ }
126
+ case "output.lovelace":
127
+ return {
128
+ summary: "The transaction pays a different amount of ADA from the one the link asks for.",
129
+ declared: formatAda(reason.declared),
130
+ actual: formatAda(reason.paid),
131
+ where: reason.address
132
+ }
133
+ case "output.assets":
134
+ return {
135
+ summary: "The transaction pays a different amount of a token from the one the link asks for.",
136
+ declared: tokenAmount(reason.declared, reason.policyId, reason.assetName),
137
+ actual: tokenAmount(reason.paid, reason.policyId, reason.assetName),
138
+ where: reason.address
139
+ }
140
+ case "certificate.missing":
141
+ return {
142
+ summary: "A certificate the link asks for is missing from the transaction.",
143
+ declared: certificates(reason.declared),
144
+ actual: certificates(reason.carried)
145
+ }
146
+ case "certificate.undeclared":
147
+ return {
148
+ summary: "The transaction carries a certificate the link never mentions.",
149
+ declared: certificates(reason.declared),
150
+ actual: certificates(reason.carried)
151
+ }
152
+ case "certificate.order":
153
+ return { summary: "The transaction's certificates are in a different order from the link's." }
154
+ case "certificate.target":
155
+ return {
156
+ summary: `Certificate ${reason.index + 1} names a different pool or DRep from the one the link asks for.`,
157
+ declared: reason.declared,
158
+ actual: reason.carried
159
+ }
160
+ case "certificate.credential":
161
+ return { summary: `Certificate ${reason.index + 1} acts on a stake key this wallet does not hold.` }
162
+ case "certificate.deposit":
163
+ return {
164
+ summary: `Certificate ${reason.index + 1} takes a deposit other than the one the network sets.`,
165
+ declared: formatAda(reason.parameter),
166
+ actual: formatAda(reason.stated)
167
+ }
168
+ case "withdrawal.missing":
169
+ return { summary: "The link asks to withdraw your rewards, and the transaction does not." }
170
+ case "withdrawal.undeclared":
171
+ return { summary: "The transaction withdraws rewards the link never mentions." }
172
+ case "withdrawal.account":
173
+ return {
174
+ summary: "The transaction withdraws from a reward account that is not this wallet's.",
175
+ where: reason.account
176
+ }
177
+ case "mint.undeclared":
178
+ return {
179
+ summary: "The transaction creates or destroys tokens, which no link can ask for.",
180
+ actual: reason.assets
181
+ .map(
182
+ (asset) =>
183
+ `${asset.quantity < 0n ? "Destroys" : "Creates"} ${tokenAmount(asset.quantity, asset.policyId, asset.assetName)}`
184
+ )
185
+ .join(", ")
186
+ }
187
+ case "body.unsupported":
188
+ return {
189
+ summary: `The transaction does things a link cannot describe: it ${reason.members
190
+ .map((member) => unsupportedWords[member])
191
+ .join(", ")}.`
192
+ }
193
+ case "fee.excessive":
194
+ return {
195
+ summary: "The network fee is higher than this transaction needs.",
196
+ declared: `At most ${formatAda(reason.ceiling)}`,
197
+ actual: formatAda(reason.fee)
198
+ }
199
+ case "interval.beyond-declared":
200
+ return {
201
+ summary: "The transaction stays valid for longer than the link says.",
202
+ declared: `Until ${formatInstant(reason.declared)}`,
203
+ actual: reason.validUntil === null ? "Never expires" : `Until ${formatInstant(reason.validUntil)}`
204
+ }
205
+ case "interval.not-yet-valid":
206
+ return {
207
+ summary: "The transaction cannot be submitted yet.",
208
+ actual: `Valid from ${formatInstant(reason.validFrom)}`
209
+ }
210
+ }
211
+ }
package/src/index.ts CHANGED
@@ -2,12 +2,13 @@
2
2
  * The public entry point of `@cardano-slips/flow`. The `exports` map exposes
3
3
  * this module and nothing else, so moving a file is never a breaking change.
4
4
  */
5
- export type { NetworkId, WalletAddress } from "./address.js"
6
- export { readWalletAddress } from "./address.js"
5
+ export type { NetworkId, OwnAddress, WalletAddress } from "./address.js"
6
+ export { readOwnAddress, readWalletAddress } from "./address.js"
7
7
  export type { BalancedTransaction, BalanceInputs, BalancingParameters } from "./balance.js"
8
8
  export { balanceIntent } from "./balance.js"
9
9
  export type { BalanceRefusal } from "./balance-error.js"
10
10
  export { BalanceError, balanceRefusals, slipErrorCodeFor as balanceErrorCodeFor } from "./balance-error.js"
11
+ export { actionLabel, actionsOf, blockedLabel, closureOf, monogramOf, originOf } from "./card.js"
11
12
  export type { CborHex, Cip30Api, Cip30ApiError, Cip30Provider, Paginate, SignedData } from "./cip30.js"
12
13
  export {
13
14
  apiErrorNames,
@@ -20,14 +21,47 @@ export {
20
21
  txSignErrorNames,
21
22
  USER_DECLINED
22
23
  } from "./cip30.js"
23
- export type { Attempt, CompletionRequest, Receipt } from "./complete.js"
24
+ export type { Attempt, CompletionRequest, Progress, Receipt } from "./complete.js"
24
25
  export { completeIntent } from "./complete.js"
25
26
  export type { CompletionRefusal } from "./complete-error.js"
26
27
  export { CompletionError, completionRefusals, slipErrorCodeFor as completionErrorCodeFor } from "./complete-error.js"
27
28
  export type { ConnectedWallet, ConnectOptions } from "./connect.js"
28
29
  export { connectWallet, networkIdFor } from "./connect.js"
30
+ export type { Derived } from "./derived.js"
29
31
  export type { DiscoveredWallet, WalletHost } from "./discovery.js"
30
32
  export { discoverWallets, findWallet, injectedUnder } from "./discovery.js"
33
+ export type { EffectsPanelProps } from "./effects-panel.js"
34
+ export { EffectsPanel } from "./effects-panel.js"
35
+ export type { ExchangeOptions, FetchedSlip, IntentRequest } from "./exchange.js"
36
+ export { ExchangeError, fetchSlip, requestIntent } from "./exchange.js"
37
+ export type { Explanation } from "./explain.js"
38
+ export {
39
+ assetLabel,
40
+ explainReason,
41
+ formatAda,
42
+ formatCountdown,
43
+ formatInstant,
44
+ formatQuantity,
45
+ secondsLeft,
46
+ shortened
47
+ } from "./explain.js"
48
+ export type { Amount, Evidence, Group, Headline, Mark, Row, Tone } from "./ledger.js"
49
+ export {
50
+ blockedLine,
51
+ describeCertificate,
52
+ evidenceOf,
53
+ groupsOf,
54
+ headlineOf,
55
+ isCollapsed,
56
+ isGrouped,
57
+ ledgerOf,
58
+ matchLine,
59
+ rawLines
60
+ } from "./ledger.js"
61
+ export type { Outcome, OutcomePanelProps, SlipReceiptProps } from "./outcome-panel.js"
62
+ export { networkNames, OutcomePanel, SlipReceipt } from "./outcome-panel.js"
63
+ export type { FieldError, ParameterFormProps } from "./parameter-form.js"
64
+ export { boundsOf, ParameterForm } from "./parameter-form.js"
31
65
  export type { KnownWallet } from "./registry.js"
32
66
  export { knownWallet, knownWallets } from "./registry.js"
33
67
  export { asResolvedInput, asResolvedInputs } from "./resolve.js"
@@ -35,6 +69,8 @@ export type { SignedTransaction, SignRequest } from "./sign.js"
35
69
  export { signTransaction, submitTransaction } from "./sign.js"
36
70
  export type { SignRefusal } from "./sign-error.js"
37
71
  export { signRefusals, SigningError, slipErrorCodeFor as signErrorCodeFor } from "./sign-error.js"
72
+ export type { SlipCardErrorProps, SlipCardProps, SlipSubmission } from "./slip-card.js"
73
+ export { SlipCard, SlipCardError, SlipCardSkeleton } from "./slip-card.js"
38
74
  export { readWalletUtxos } from "./utxo.js"
39
75
  export type { ConnectRefusal } from "./wallet-error.js"
40
76
  export { connectRefusals, slipErrorCodeFor, WalletConnectError } from "./wallet-error.js"