@farcaster/snap 2.8.0 → 2.10.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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/catalog-renderer.d.ts +5 -5
- package/dist/react/catalog-renderer.js +16 -4
- package/dist/react/components/action-button.js +23 -5
- package/dist/react/index.d.ts +2 -13
- package/dist/react/snap-view-core.js +90 -45
- package/dist/react/v1/snap-view.js +1 -1
- package/dist/react/v2/snap-view.js +1 -1
- package/dist/react-native/components/snap-action-button.js +6 -1
- package/dist/react-native/snap-view-core.js +77 -44
- package/dist/react-native/types.d.ts +2 -13
- package/dist/render-state.d.ts +9 -0
- package/dist/render-state.js +27 -0
- package/dist/schemas.d.ts +123 -3
- package/dist/schemas.js +53 -2
- package/dist/server/parseRequest.js +19 -3
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.js +1 -0
- package/dist/ui/catalog.d.ts +13 -14
- package/dist/ui/catalog.js +15 -22
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/react/catalog-renderer.tsx +57 -3
- package/src/react/components/action-button.tsx +32 -3
- package/src/react/index.tsx +4 -14
- package/src/react/snap-view-core.tsx +144 -48
- package/src/react/v1/snap-view.tsx +1 -0
- package/src/react/v2/snap-view.tsx +1 -0
- package/src/react-native/components/snap-action-button.tsx +6 -1
- package/src/react-native/snap-view-core.tsx +114 -48
- package/src/react-native/types.ts +4 -14
- package/src/render-state.ts +46 -0
- package/src/schemas.ts +73 -2
- package/src/server/parseRequest.ts +37 -6
- package/src/ui/button.ts +1 -0
- package/src/ui/catalog.ts +16 -25
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createStateStore } from "@json-render/react-native";
|
|
2
3
|
import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
|
|
3
4
|
import { SnapCatalogView } from "./catalog-renderer.js";
|
|
4
5
|
import { ConfettiOverlay } from "./confetti-overlay.js";
|
|
@@ -8,7 +9,7 @@ import { SnapVersionProvider } from "./snap-version-context.js";
|
|
|
8
9
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
9
10
|
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
|
10
11
|
import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
|
|
11
|
-
import {
|
|
12
|
+
import { buildActionActivityStateChanges, buildInitialRenderState, cloneSnapRenderState, getUnpresentedSnapEffects, hasPendingSnapAction, markSnapEffectsPresented, } from "../render-state.js";
|
|
12
13
|
function asRecord(value) {
|
|
13
14
|
return value && typeof value === "object"
|
|
14
15
|
? value
|
|
@@ -17,6 +18,11 @@ function asRecord(value) {
|
|
|
17
18
|
function optionalString(value) {
|
|
18
19
|
return value ? String(value) : undefined;
|
|
19
20
|
}
|
|
21
|
+
function recordValue(value) {
|
|
22
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
23
|
+
? value
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
20
26
|
function withDefaultElementProps(spec) {
|
|
21
27
|
if (!spec || typeof spec !== "object" || !("elements" in spec))
|
|
22
28
|
return spec;
|
|
@@ -54,10 +60,26 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
54
60
|
initialRenderState,
|
|
55
61
|
themeAccent: snap.theme?.accent,
|
|
56
62
|
}), [initialRenderState, spec.state, snap.theme?.accent]);
|
|
63
|
+
const stateStore = useMemo(() => createStateStore(initialState), [
|
|
64
|
+
initialState,
|
|
65
|
+
]);
|
|
57
66
|
const stateRef = useRef(initialState);
|
|
67
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
68
|
+
const pendingActionCountRef = useRef(0);
|
|
69
|
+
const [hasPendingAction, setHasPendingAction] = useState(false);
|
|
70
|
+
const [actionActivityVersion, setActionActivityVersion] = useState(0);
|
|
58
71
|
useEffect(() => {
|
|
59
72
|
stateRef.current = cloneSnapRenderState(initialState);
|
|
60
73
|
}, [initialState]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
76
|
+
}, [onRenderStateChange]);
|
|
77
|
+
useEffect(() => stateStore.subscribe(() => {
|
|
78
|
+
const snapshot = cloneSnapRenderState(stateStore.getSnapshot());
|
|
79
|
+
stateRef.current = snapshot;
|
|
80
|
+
setHasPendingAction(hasPendingSnapAction(snapshot));
|
|
81
|
+
onRenderStateChangeRef.current?.(snapshot);
|
|
82
|
+
}), [stateStore]);
|
|
61
83
|
useEffect(() => {
|
|
62
84
|
const catalogResult = snapJsonRenderCatalog.validate(spec);
|
|
63
85
|
if (!catalogResult.success) {
|
|
@@ -77,10 +99,6 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
77
99
|
confetti: 0,
|
|
78
100
|
fireworks: 0,
|
|
79
101
|
});
|
|
80
|
-
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
onRenderStateChangeRef.current = onRenderStateChange;
|
|
83
|
-
}, [onRenderStateChange]);
|
|
84
102
|
useEffect(() => {
|
|
85
103
|
const effectsToPresent = getUnpresentedSnapEffects(stateRef.current, snapEffects);
|
|
86
104
|
if (effectsToPresent.length === 0) {
|
|
@@ -97,7 +115,10 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
97
115
|
return;
|
|
98
116
|
}
|
|
99
117
|
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
100
|
-
|
|
118
|
+
const meta = recordValue(stateRef.current.__snapRender);
|
|
119
|
+
stateStore.update({
|
|
120
|
+
"/__snapRender/presentedEffects": meta?.presentedEffects ?? [],
|
|
121
|
+
});
|
|
101
122
|
}
|
|
102
123
|
setEffectRunKeys((current) => ({
|
|
103
124
|
confetti: effectsToPresent.includes("confetti")
|
|
@@ -111,47 +132,70 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
111
132
|
? current.fireworks
|
|
112
133
|
: 0,
|
|
113
134
|
}));
|
|
114
|
-
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
135
|
+
}, [initialState, showConfetti, showFireworks, snapEffects, stateStore]);
|
|
115
136
|
const handlersRef = useRef(handlers);
|
|
116
137
|
handlersRef.current = handlers;
|
|
138
|
+
const applyActionActivityState = useCallback((name, params, pending) => {
|
|
139
|
+
stateStore.update(Object.fromEntries(buildActionActivityStateChanges({
|
|
140
|
+
actionName: name,
|
|
141
|
+
params,
|
|
142
|
+
pending,
|
|
143
|
+
}).map(({ path, value }) => [path, value])));
|
|
144
|
+
}, [stateStore]);
|
|
145
|
+
const setActionPending = useCallback((name, params) => {
|
|
146
|
+
pendingActionCountRef.current += 1;
|
|
147
|
+
setHasPendingAction(true);
|
|
148
|
+
setActionActivityVersion((version) => version + 1);
|
|
149
|
+
applyActionActivityState(name, params, true);
|
|
150
|
+
}, [applyActionActivityState]);
|
|
151
|
+
const setActionSettled = useCallback((name, params) => {
|
|
152
|
+
pendingActionCountRef.current = Math.max(0, pendingActionCountRef.current - 1);
|
|
153
|
+
applyActionActivityState(name, params, false);
|
|
154
|
+
if (pendingActionCountRef.current === 0) {
|
|
155
|
+
setHasPendingAction(false);
|
|
156
|
+
}
|
|
157
|
+
setActionActivityVersion((version) => version + 1);
|
|
158
|
+
}, [applyActionActivityState]);
|
|
117
159
|
const handleAction = useCallback((name, params) => {
|
|
118
160
|
const inputs = (stateRef.current.inputs ?? {});
|
|
119
161
|
const p = (params ?? {});
|
|
120
162
|
const h = handlersRef.current;
|
|
163
|
+
let result;
|
|
164
|
+
setActionPending(name, p);
|
|
121
165
|
switch (name) {
|
|
122
166
|
case "submit":
|
|
123
|
-
h.submit(String(p.target ?? ""), inputs);
|
|
167
|
+
result = h.submit(String(p.target ?? ""), inputs);
|
|
124
168
|
break;
|
|
125
169
|
case "open_url":
|
|
126
|
-
h.open_url(String(p.target ?? ""));
|
|
170
|
+
result = h.open_url(String(p.target ?? ""));
|
|
127
171
|
break;
|
|
128
172
|
case "open_snap":
|
|
129
|
-
h.open_snap(String(p.target ?? ""));
|
|
173
|
+
result = h.open_snap(String(p.target ?? ""));
|
|
130
174
|
break;
|
|
131
175
|
case "open_mini_app":
|
|
132
|
-
h.open_mini_app(String(p.target ?? ""));
|
|
176
|
+
result = h.open_mini_app(String(p.target ?? ""));
|
|
133
177
|
break;
|
|
134
178
|
case "view_cast":
|
|
135
|
-
h.view_cast({ hash: String(p.hash ?? "") });
|
|
179
|
+
result = h.view_cast({ hash: String(p.hash ?? "") });
|
|
136
180
|
break;
|
|
137
181
|
case "view_profile":
|
|
138
|
-
h.view_profile({ fid: Number(p.fid ?? 0) });
|
|
182
|
+
result = h.view_profile({ fid: Number(p.fid ?? 0) });
|
|
139
183
|
break;
|
|
140
184
|
case "view_channel":
|
|
141
|
-
h.view_channel({ channelKey: String(p.channelKey ?? "") });
|
|
185
|
+
result = h.view_channel({ channelKey: String(p.channelKey ?? "") });
|
|
142
186
|
break;
|
|
143
187
|
case "compose_cast":
|
|
144
|
-
h.compose_cast({
|
|
188
|
+
result = h.compose_cast({
|
|
145
189
|
text: p.text ? String(p.text) : undefined,
|
|
146
190
|
channelKey: p.channelKey ? String(p.channelKey) : undefined,
|
|
147
191
|
embeds: Array.isArray(p.embeds) ? p.embeds : undefined,
|
|
148
192
|
});
|
|
149
193
|
break;
|
|
150
194
|
case "view_token":
|
|
151
|
-
h.view_token({ token: String(p.token ?? "") });
|
|
195
|
+
result = h.view_token({ token: String(p.token ?? "") });
|
|
152
196
|
break;
|
|
153
197
|
case "send_token":
|
|
154
|
-
h.send_token({
|
|
198
|
+
result = h.send_token({
|
|
155
199
|
token: String(p.token ?? ""),
|
|
156
200
|
amount: p.amount ? String(p.amount) : undefined,
|
|
157
201
|
recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
|
|
@@ -161,13 +205,13 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
161
205
|
});
|
|
162
206
|
break;
|
|
163
207
|
case "swap_token":
|
|
164
|
-
h.swap_token({
|
|
208
|
+
result = h.swap_token({
|
|
165
209
|
sellToken: p.sellToken ? String(p.sellToken) : undefined,
|
|
166
210
|
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
167
211
|
});
|
|
168
212
|
break;
|
|
169
213
|
case "send_transaction":
|
|
170
|
-
h.send_transaction?.({
|
|
214
|
+
result = h.send_transaction?.({
|
|
171
215
|
chainId: String(p.chainId ?? ""),
|
|
172
216
|
to: String(p.to ?? ""),
|
|
173
217
|
data: optionalString(p.data),
|
|
@@ -178,34 +222,23 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
178
222
|
maxPriorityFeePerGas: optionalString(p.maxPriorityFeePerGas),
|
|
179
223
|
});
|
|
180
224
|
break;
|
|
181
|
-
case "send_calls":
|
|
182
|
-
h.send_calls?.({
|
|
183
|
-
version: p.version === "1.0" ? "1.0" : undefined,
|
|
184
|
-
chainId: String(p.chainId ?? ""),
|
|
185
|
-
atomicRequired: typeof p.atomicRequired === "boolean"
|
|
186
|
-
? p.atomicRequired
|
|
187
|
-
: undefined,
|
|
188
|
-
id: optionalString(p.id),
|
|
189
|
-
calls: Array.isArray(p.calls)
|
|
190
|
-
? p.calls.map((call) => {
|
|
191
|
-
const c = asRecord(call);
|
|
192
|
-
return {
|
|
193
|
-
to: optionalString(c.to),
|
|
194
|
-
data: optionalString(c.data),
|
|
195
|
-
value: optionalString(c.value),
|
|
196
|
-
};
|
|
197
|
-
})
|
|
198
|
-
: [],
|
|
199
|
-
});
|
|
200
|
-
break;
|
|
201
225
|
default:
|
|
202
226
|
break;
|
|
203
227
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
228
|
+
if (result instanceof Promise) {
|
|
229
|
+
void result.finally(() => {
|
|
230
|
+
setActionSettled(name, p);
|
|
231
|
+
}).catch(() => { });
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
setActionSettled(name, p);
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}, [setActionPending, setActionSettled]);
|
|
238
|
+
const showLoadingOverlay = loading ||
|
|
239
|
+
hasPendingAction ||
|
|
240
|
+
(actionActivityVersion >= 0 && pendingActionCountRef.current > 0);
|
|
241
|
+
return (_jsxs(View, { style: styles.container, onStartShouldSetResponderCapture: () => hasPendingSnapAction(stateRef.current), children: [showLoadingOverlay ? (loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })) : (loadingOverlay)) : null, _jsx(SnapVersionProvider, { value: snap.version === "2.0" ? "2.0" : "1.0", children: _jsx(SnapCatalogView, { spec: spec, store: stateStore, loading: false, onAction: handleAction }, pageKey) }), showConfetti && effectRunKeys.confetti > 0 && (_jsx(ConfettiOverlay, {}, effectRunKeys.confetti)), showFireworks && effectRunKeys.fireworks > 0 && (_jsx(FireworksOverlay, {}, effectRunKeys.fireworks))] }));
|
|
209
242
|
}
|
|
210
243
|
export function SnapLoadingOverlay({ appearance, accentHex, }) {
|
|
211
244
|
return (_jsx(View, { style: [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Spec } from "@json-render/core";
|
|
2
2
|
import type { SnapRenderState } from "../render-state.js";
|
|
3
|
+
import type { SnapTransactionResult } from "../schemas.js";
|
|
3
4
|
export type { SnapRenderState };
|
|
4
5
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
6
|
[key: string]: JsonValue;
|
|
@@ -22,17 +23,6 @@ export type SnapSendTransactionParams = {
|
|
|
22
23
|
maxFeePerGas?: string;
|
|
23
24
|
maxPriorityFeePerGas?: string;
|
|
24
25
|
};
|
|
25
|
-
export type SnapSendCallsParams = {
|
|
26
|
-
version?: "1.0";
|
|
27
|
-
chainId: string;
|
|
28
|
-
atomicRequired?: boolean;
|
|
29
|
-
id?: string;
|
|
30
|
-
calls: Array<{
|
|
31
|
-
to?: string;
|
|
32
|
-
data?: string;
|
|
33
|
-
value?: string;
|
|
34
|
-
}>;
|
|
35
|
-
};
|
|
36
26
|
export type SnapActionHandlers = {
|
|
37
27
|
submit: (target: string, inputs: Record<string, JsonValue>) => void;
|
|
38
28
|
open_url: (target: string) => void;
|
|
@@ -65,6 +55,5 @@ export type SnapActionHandlers = {
|
|
|
65
55
|
sellToken?: string;
|
|
66
56
|
buyToken?: string;
|
|
67
57
|
}) => void;
|
|
68
|
-
send_transaction?: (params: SnapSendTransactionParams) => void
|
|
69
|
-
send_calls?: (params: SnapSendCallsParams) => void;
|
|
58
|
+
send_transaction?: (params: SnapSendTransactionParams) => void | Promise<void | SnapTransactionResult>;
|
|
70
59
|
};
|
package/dist/render-state.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ export type SnapRenderStateChanges = {
|
|
|
5
5
|
}[] | Record<string, unknown> | null | undefined;
|
|
6
6
|
export declare function cloneSnapRenderState<T>(value: T): T;
|
|
7
7
|
export declare function applyStatePaths(model: Record<string, unknown>, changes: SnapRenderStateChanges): void;
|
|
8
|
+
export declare function buildActionActivityStateChanges({ actionName, params, pending, }: {
|
|
9
|
+
actionName: unknown;
|
|
10
|
+
params: Record<string, unknown>;
|
|
11
|
+
pending: boolean;
|
|
12
|
+
}): {
|
|
13
|
+
path: string;
|
|
14
|
+
value: unknown;
|
|
15
|
+
}[];
|
|
16
|
+
export declare function hasPendingSnapAction(model: SnapRenderState): boolean;
|
|
8
17
|
export declare function getUnpresentedSnapEffects(model: SnapRenderState, effects: readonly string[] | undefined): string[];
|
|
9
18
|
export declare function markSnapEffectsPresented(model: SnapRenderState, effects: readonly string[] | undefined): boolean;
|
|
10
19
|
export declare function buildInitialRenderState({ specState, initialRenderState, themeAccent, }: {
|
package/dist/render-state.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const SNAP_RENDER_STATE_META_KEY = "__snapRender";
|
|
2
|
+
const ACTION_ACTIVITY_KEY_MAX_LENGTH = 64;
|
|
2
3
|
function isRecord(value) {
|
|
3
4
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
5
|
}
|
|
@@ -75,6 +76,32 @@ export function applyStatePaths(model, changes) {
|
|
|
75
76
|
setStateValue(model, parts, value);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
function sanitizeActionActivityKey(value) {
|
|
80
|
+
const sanitized = value
|
|
81
|
+
.trim()
|
|
82
|
+
.slice(0, ACTION_ACTIVITY_KEY_MAX_LENGTH)
|
|
83
|
+
.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
84
|
+
return sanitized || "action";
|
|
85
|
+
}
|
|
86
|
+
function getActionActivityKey(actionName, params) {
|
|
87
|
+
const explicitKey = params.activityKey;
|
|
88
|
+
return sanitizeActionActivityKey(typeof explicitKey === "string" && explicitKey.trim()
|
|
89
|
+
? explicitKey
|
|
90
|
+
: String(actionName || "action"));
|
|
91
|
+
}
|
|
92
|
+
export function buildActionActivityStateChanges({ actionName, params, pending, }) {
|
|
93
|
+
const key = getActionActivityKey(actionName, params);
|
|
94
|
+
return [
|
|
95
|
+
{ path: `/actions/${key}/name`, value: String(actionName || "action") },
|
|
96
|
+
{ path: `/actions/${key}/pending`, value: pending },
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
export function hasPendingSnapAction(model) {
|
|
100
|
+
const actions = model.actions;
|
|
101
|
+
if (!isRecord(actions))
|
|
102
|
+
return false;
|
|
103
|
+
return Object.values(actions).some((action) => isRecord(action) && action.pending === true);
|
|
104
|
+
}
|
|
78
105
|
export function getUnpresentedSnapEffects(model, effects) {
|
|
79
106
|
const presentedEffects = getPresentedSnapEffects(model);
|
|
80
107
|
return normalizeEffects(effects).filter((effect) => !presentedEffects.has(effect));
|
package/dist/schemas.d.ts
CHANGED
|
@@ -69,9 +69,34 @@ export type SnapHandlerResult = {
|
|
|
69
69
|
effects?: z.input<typeof snapResponseSchema>["effects"];
|
|
70
70
|
ui: SnapSpecInput;
|
|
71
71
|
};
|
|
72
|
+
declare const snapSendTransactionParamsSchema: z.ZodObject<{
|
|
73
|
+
chainId: z.ZodString;
|
|
74
|
+
to: z.ZodString;
|
|
75
|
+
data: z.ZodOptional<z.ZodString>;
|
|
76
|
+
value: z.ZodOptional<z.ZodString>;
|
|
77
|
+
gas: z.ZodOptional<z.ZodString>;
|
|
78
|
+
gasPrice: z.ZodOptional<z.ZodString>;
|
|
79
|
+
maxFeePerGas: z.ZodOptional<z.ZodString>;
|
|
80
|
+
maxPriorityFeePerGas: z.ZodOptional<z.ZodString>;
|
|
81
|
+
}, z.core.$strict>;
|
|
82
|
+
export type SnapSendTransactionParams = z.infer<typeof snapSendTransactionParamsSchema>;
|
|
83
|
+
export declare const snapTransactionResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
84
|
+
success: z.ZodLiteral<true>;
|
|
85
|
+
transactionHash: z.ZodString;
|
|
86
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
87
|
+
success: z.ZodLiteral<false>;
|
|
88
|
+
reason: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
unknown: "unknown";
|
|
90
|
+
rejected_by_user: "rejected_by_user";
|
|
91
|
+
failed: "failed";
|
|
92
|
+
}>>>;
|
|
93
|
+
message: z.ZodOptional<z.ZodString>;
|
|
94
|
+
code: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
95
|
+
transactionHash: z.ZodOptional<z.ZodString>;
|
|
96
|
+
}, z.core.$strict>], "success">;
|
|
97
|
+
export type SnapTransactionResult = z.infer<typeof snapTransactionResultSchema>;
|
|
72
98
|
export declare const payloadSchema: z.ZodObject<{
|
|
73
99
|
fid: z.ZodOptional<z.ZodNumber>;
|
|
74
|
-
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
75
100
|
timestamp: z.ZodNumber;
|
|
76
101
|
audience: z.ZodString;
|
|
77
102
|
user: z.ZodObject<{
|
|
@@ -88,6 +113,7 @@ export declare const payloadSchema: z.ZodObject<{
|
|
|
88
113
|
}, z.core.$strip>, z.ZodObject<{
|
|
89
114
|
type: z.ZodLiteral<"standalone">;
|
|
90
115
|
}, z.core.$strip>], "type">;
|
|
116
|
+
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
91
117
|
}, z.core.$strip>;
|
|
92
118
|
export type SnapPayload = z.infer<typeof payloadSchema>;
|
|
93
119
|
/** JFS payload shape for POST minus deprecated `fid`; used for GET auth via payload header. */
|
|
@@ -112,6 +138,7 @@ export declare const getPayloadSchema: z.ZodObject<{
|
|
|
112
138
|
export type SnapGetPayload = z.infer<typeof getPayloadSchema>;
|
|
113
139
|
export declare const ACTION_TYPE_GET: "get";
|
|
114
140
|
export declare const ACTION_TYPE_POST: "post";
|
|
141
|
+
export declare const ACTION_TYPE_TRANSACTION_RESULT: "transaction_result";
|
|
115
142
|
declare const snapGetActionSchema: z.ZodObject<{
|
|
116
143
|
type: z.ZodLiteral<"get">;
|
|
117
144
|
user: z.ZodOptional<z.ZodObject<{
|
|
@@ -134,7 +161,6 @@ declare const snapGetActionSchema: z.ZodObject<{
|
|
|
134
161
|
export type SnapGetAction = z.infer<typeof snapGetActionSchema>;
|
|
135
162
|
declare const snapPostActionSchema: z.ZodObject<{
|
|
136
163
|
fid: z.ZodOptional<z.ZodNumber>;
|
|
137
|
-
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
138
164
|
timestamp: z.ZodNumber;
|
|
139
165
|
audience: z.ZodString;
|
|
140
166
|
user: z.ZodObject<{
|
|
@@ -151,9 +177,58 @@ declare const snapPostActionSchema: z.ZodObject<{
|
|
|
151
177
|
}, z.core.$strip>, z.ZodObject<{
|
|
152
178
|
type: z.ZodLiteral<"standalone">;
|
|
153
179
|
}, z.core.$strip>], "type">;
|
|
180
|
+
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
154
181
|
type: z.ZodLiteral<"post">;
|
|
155
182
|
}, z.core.$strip>;
|
|
156
183
|
export type SnapPostAction = z.infer<typeof snapPostActionSchema>;
|
|
184
|
+
export declare const transactionResultPayloadSchema: z.ZodObject<{
|
|
185
|
+
fid: z.ZodOptional<z.ZodNumber>;
|
|
186
|
+
timestamp: z.ZodNumber;
|
|
187
|
+
audience: z.ZodString;
|
|
188
|
+
user: z.ZodObject<{
|
|
189
|
+
fid: z.ZodNumber;
|
|
190
|
+
}, z.core.$strip>;
|
|
191
|
+
surface: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
192
|
+
type: z.ZodLiteral<"cast">;
|
|
193
|
+
cast: z.ZodObject<{
|
|
194
|
+
hash: z.ZodString;
|
|
195
|
+
author: z.ZodObject<{
|
|
196
|
+
fid: z.ZodNumber;
|
|
197
|
+
}, z.core.$strip>;
|
|
198
|
+
}, z.core.$strip>;
|
|
199
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
200
|
+
type: z.ZodLiteral<"standalone">;
|
|
201
|
+
}, z.core.$strip>], "type">;
|
|
202
|
+
type: z.ZodLiteral<"transaction_result">;
|
|
203
|
+
transaction: z.ZodObject<{
|
|
204
|
+
request: z.ZodObject<{
|
|
205
|
+
chainId: z.ZodString;
|
|
206
|
+
to: z.ZodString;
|
|
207
|
+
data: z.ZodOptional<z.ZodString>;
|
|
208
|
+
value: z.ZodOptional<z.ZodString>;
|
|
209
|
+
gas: z.ZodOptional<z.ZodString>;
|
|
210
|
+
gasPrice: z.ZodOptional<z.ZodString>;
|
|
211
|
+
maxFeePerGas: z.ZodOptional<z.ZodString>;
|
|
212
|
+
maxPriorityFeePerGas: z.ZodOptional<z.ZodString>;
|
|
213
|
+
}, z.core.$strict>;
|
|
214
|
+
result: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
215
|
+
success: z.ZodLiteral<true>;
|
|
216
|
+
transactionHash: z.ZodString;
|
|
217
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
218
|
+
success: z.ZodLiteral<false>;
|
|
219
|
+
reason: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
220
|
+
unknown: "unknown";
|
|
221
|
+
rejected_by_user: "rejected_by_user";
|
|
222
|
+
failed: "failed";
|
|
223
|
+
}>>>;
|
|
224
|
+
message: z.ZodOptional<z.ZodString>;
|
|
225
|
+
code: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
226
|
+
transactionHash: z.ZodOptional<z.ZodString>;
|
|
227
|
+
}, z.core.$strict>], "success">;
|
|
228
|
+
}, z.core.$strict>;
|
|
229
|
+
}, z.core.$strip>;
|
|
230
|
+
export type SnapTransactionResultPayload = z.infer<typeof transactionResultPayloadSchema>;
|
|
231
|
+
export type SnapTransactionResultAction = SnapTransactionResultPayload;
|
|
157
232
|
export declare const snapActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
158
233
|
type: z.ZodLiteral<"get">;
|
|
159
234
|
user: z.ZodOptional<z.ZodObject<{
|
|
@@ -174,7 +249,6 @@ export declare const snapActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
174
249
|
}, z.core.$strip>], "type">>;
|
|
175
250
|
}, z.core.$strip>, z.ZodObject<{
|
|
176
251
|
fid: z.ZodOptional<z.ZodNumber>;
|
|
177
|
-
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
178
252
|
timestamp: z.ZodNumber;
|
|
179
253
|
audience: z.ZodString;
|
|
180
254
|
user: z.ZodObject<{
|
|
@@ -191,7 +265,53 @@ export declare const snapActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
191
265
|
}, z.core.$strip>, z.ZodObject<{
|
|
192
266
|
type: z.ZodLiteral<"standalone">;
|
|
193
267
|
}, z.core.$strip>], "type">;
|
|
268
|
+
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
194
269
|
type: z.ZodLiteral<"post">;
|
|
270
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
271
|
+
fid: z.ZodOptional<z.ZodNumber>;
|
|
272
|
+
timestamp: z.ZodNumber;
|
|
273
|
+
audience: z.ZodString;
|
|
274
|
+
user: z.ZodObject<{
|
|
275
|
+
fid: z.ZodNumber;
|
|
276
|
+
}, z.core.$strip>;
|
|
277
|
+
surface: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
278
|
+
type: z.ZodLiteral<"cast">;
|
|
279
|
+
cast: z.ZodObject<{
|
|
280
|
+
hash: z.ZodString;
|
|
281
|
+
author: z.ZodObject<{
|
|
282
|
+
fid: z.ZodNumber;
|
|
283
|
+
}, z.core.$strip>;
|
|
284
|
+
}, z.core.$strip>;
|
|
285
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
286
|
+
type: z.ZodLiteral<"standalone">;
|
|
287
|
+
}, z.core.$strip>], "type">;
|
|
288
|
+
type: z.ZodLiteral<"transaction_result">;
|
|
289
|
+
transaction: z.ZodObject<{
|
|
290
|
+
request: z.ZodObject<{
|
|
291
|
+
chainId: z.ZodString;
|
|
292
|
+
to: z.ZodString;
|
|
293
|
+
data: z.ZodOptional<z.ZodString>;
|
|
294
|
+
value: z.ZodOptional<z.ZodString>;
|
|
295
|
+
gas: z.ZodOptional<z.ZodString>;
|
|
296
|
+
gasPrice: z.ZodOptional<z.ZodString>;
|
|
297
|
+
maxFeePerGas: z.ZodOptional<z.ZodString>;
|
|
298
|
+
maxPriorityFeePerGas: z.ZodOptional<z.ZodString>;
|
|
299
|
+
}, z.core.$strict>;
|
|
300
|
+
result: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
301
|
+
success: z.ZodLiteral<true>;
|
|
302
|
+
transactionHash: z.ZodString;
|
|
303
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
304
|
+
success: z.ZodLiteral<false>;
|
|
305
|
+
reason: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
306
|
+
unknown: "unknown";
|
|
307
|
+
rejected_by_user: "rejected_by_user";
|
|
308
|
+
failed: "failed";
|
|
309
|
+
}>>>;
|
|
310
|
+
message: z.ZodOptional<z.ZodString>;
|
|
311
|
+
code: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
312
|
+
transactionHash: z.ZodOptional<z.ZodString>;
|
|
313
|
+
}, z.core.$strict>], "success">;
|
|
314
|
+
}, z.core.$strict>;
|
|
195
315
|
}, z.core.$strip>], "type">;
|
|
196
316
|
export type SnapAction = z.infer<typeof snapActionSchema>;
|
|
197
317
|
export type SnapContext = {
|
package/dist/schemas.js
CHANGED
|
@@ -49,20 +49,59 @@ const surfaceSchema = z.discriminatedUnion("type", [
|
|
|
49
49
|
]);
|
|
50
50
|
const fidSchema = z.number().int().nonnegative();
|
|
51
51
|
const userSchema = z.object({ fid: fidSchema });
|
|
52
|
-
|
|
52
|
+
const basePayloadSchema = z
|
|
53
53
|
.object({
|
|
54
54
|
fid: fidSchema.optional(), // deprecated in favor of user.fid
|
|
55
|
-
inputs: z.record(z.string(), postInputValueSchema).default({}),
|
|
56
55
|
timestamp: z.number().int(),
|
|
57
56
|
audience: z.string(),
|
|
58
57
|
user: userSchema,
|
|
59
58
|
surface: surfaceSchema,
|
|
60
59
|
})
|
|
61
60
|
.strip();
|
|
61
|
+
const snapSendTransactionParamsSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
chainId: z.string(),
|
|
64
|
+
to: z.string(),
|
|
65
|
+
data: z.string().optional(),
|
|
66
|
+
value: z.string().optional(),
|
|
67
|
+
gas: z.string().optional(),
|
|
68
|
+
gasPrice: z.string().optional(),
|
|
69
|
+
maxFeePerGas: z.string().optional(),
|
|
70
|
+
maxPriorityFeePerGas: z.string().optional(),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
const snapTransactionSuccessSchema = z
|
|
74
|
+
.object({
|
|
75
|
+
success: z.literal(true),
|
|
76
|
+
transactionHash: z.string(),
|
|
77
|
+
})
|
|
78
|
+
.strict();
|
|
79
|
+
const snapTransactionFailureSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
success: z.literal(false),
|
|
82
|
+
reason: z
|
|
83
|
+
.enum(["rejected_by_user", "failed", "unknown"])
|
|
84
|
+
.optional()
|
|
85
|
+
.default("unknown"),
|
|
86
|
+
message: z.string().optional(),
|
|
87
|
+
code: z.union([z.string(), z.number()]).optional(),
|
|
88
|
+
transactionHash: z.string().optional(),
|
|
89
|
+
})
|
|
90
|
+
.strict();
|
|
91
|
+
export const snapTransactionResultSchema = z.discriminatedUnion("success", [
|
|
92
|
+
snapTransactionSuccessSchema,
|
|
93
|
+
snapTransactionFailureSchema,
|
|
94
|
+
]);
|
|
95
|
+
export const payloadSchema = basePayloadSchema
|
|
96
|
+
.extend({
|
|
97
|
+
inputs: z.record(z.string(), postInputValueSchema).default({}),
|
|
98
|
+
})
|
|
99
|
+
.strip();
|
|
62
100
|
/** JFS payload shape for POST minus deprecated `fid`; used for GET auth via payload header. */
|
|
63
101
|
export const getPayloadSchema = payloadSchema.omit({ inputs: true, fid: true });
|
|
64
102
|
export const ACTION_TYPE_GET = "get";
|
|
65
103
|
export const ACTION_TYPE_POST = "post";
|
|
104
|
+
export const ACTION_TYPE_TRANSACTION_RESULT = "transaction_result";
|
|
66
105
|
const snapGetActionSchema = z.object({
|
|
67
106
|
type: z.literal(ACTION_TYPE_GET),
|
|
68
107
|
user: userSchema.optional(),
|
|
@@ -73,7 +112,19 @@ const snapGetActionSchema = z.object({
|
|
|
73
112
|
const snapPostActionSchema = payloadSchema.extend({
|
|
74
113
|
type: z.literal(ACTION_TYPE_POST),
|
|
75
114
|
});
|
|
115
|
+
export const transactionResultPayloadSchema = basePayloadSchema
|
|
116
|
+
.extend({
|
|
117
|
+
type: z.literal(ACTION_TYPE_TRANSACTION_RESULT),
|
|
118
|
+
transaction: z
|
|
119
|
+
.object({
|
|
120
|
+
request: snapSendTransactionParamsSchema,
|
|
121
|
+
result: snapTransactionResultSchema,
|
|
122
|
+
})
|
|
123
|
+
.strict(),
|
|
124
|
+
})
|
|
125
|
+
.strip();
|
|
76
126
|
export const snapActionSchema = z.discriminatedUnion("type", [
|
|
77
127
|
snapGetActionSchema,
|
|
78
128
|
snapPostActionSchema,
|
|
129
|
+
transactionResultPayloadSchema,
|
|
79
130
|
]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ACTION_TYPE_GET, ACTION_TYPE_POST, getPayloadSchema, payloadSchema, } from "../schemas.js";
|
|
1
|
+
import { ACTION_TYPE_TRANSACTION_RESULT, ACTION_TYPE_GET, ACTION_TYPE_POST, getPayloadSchema, payloadSchema, transactionResultPayloadSchema, } from "../schemas.js";
|
|
2
2
|
import { decodePayload, parseJfs, verifyJFS } from "./verify.js";
|
|
3
3
|
import { SNAP_PAYLOAD_HEADER } from "../constants.js";
|
|
4
4
|
const DEFAULT_SNAP_POST_MAX_SKEW_SECONDS = 300;
|
|
@@ -46,7 +46,9 @@ async function parseGetRequest(request, options) {
|
|
|
46
46
|
async function parsePostRequest(request, options) {
|
|
47
47
|
const result = await validateJfsPayload({
|
|
48
48
|
jfsText: await request.text(),
|
|
49
|
-
schema:
|
|
49
|
+
schema: (decodedPayload) => isTransactionResultPayload(decodedPayload)
|
|
50
|
+
? transactionResultPayloadSchema
|
|
51
|
+
: payloadSchema,
|
|
50
52
|
request,
|
|
51
53
|
options,
|
|
52
54
|
});
|
|
@@ -63,11 +65,23 @@ async function parsePostRequest(request, options) {
|
|
|
63
65
|
},
|
|
64
66
|
};
|
|
65
67
|
}
|
|
68
|
+
if (isTransactionResultPayload(payload)) {
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
action: payload,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
66
74
|
return {
|
|
67
75
|
success: true,
|
|
68
76
|
action: { type: ACTION_TYPE_POST, ...payload },
|
|
69
77
|
};
|
|
70
78
|
}
|
|
79
|
+
function isTransactionResultPayload(payload) {
|
|
80
|
+
return (payload !== null &&
|
|
81
|
+
typeof payload === "object" &&
|
|
82
|
+
"type" in payload &&
|
|
83
|
+
payload.type === ACTION_TYPE_TRANSACTION_RESULT);
|
|
84
|
+
}
|
|
71
85
|
/**
|
|
72
86
|
* Shared pipeline for authenticated snap requests: parse the JFS envelope,
|
|
73
87
|
* decode and schema-validate the payload, optionally verify the JFS signature
|
|
@@ -88,7 +102,9 @@ async function validateJfsPayload({ jfsText, schema, request, options, invalidJs
|
|
|
88
102
|
};
|
|
89
103
|
}
|
|
90
104
|
const jfs = parsed.jfs;
|
|
91
|
-
const
|
|
105
|
+
const decodedPayload = decodePayload(jfs.payload);
|
|
106
|
+
const selectedSchema = typeof schema === "function" ? schema(decodedPayload) : schema;
|
|
107
|
+
const payloadParsed = selectedSchema.safeParse(decodedPayload);
|
|
92
108
|
if (!payloadParsed.success) {
|
|
93
109
|
return {
|
|
94
110
|
ok: false,
|
package/dist/ui/button.d.ts
CHANGED