@xochi/client 0.1.1 → 0.1.2
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/CHANGELOG.md +10 -0
- package/README.md +1 -0
- package/dist/cjs/machine/machine.d.ts +2 -0
- package/dist/cjs/machine/machine.js +19 -7
- package/dist/cjs/shared/index.d.ts +1 -0
- package/dist/cjs/shared/index.js +1 -0
- package/dist/cjs/shared/shieldedPreview.d.ts +841 -0
- package/dist/cjs/shared/shieldedPreview.js +128 -0
- package/dist/esm/machine/machine.d.ts +2 -0
- package/dist/esm/machine/machine.js +19 -7
- package/dist/esm/shared/index.d.ts +1 -0
- package/dist/esm/shared/index.js +1 -0
- package/dist/esm/shared/shieldedPreview.d.ts +841 -0
- package/dist/esm/shared/shieldedPreview.js +124 -0
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Riddler's shielded terms preview wire (`POST /xochi/shielded/preview`,
|
|
4
|
+
* Riddler#1296): the typed request and response shapes and a decoder.
|
|
5
|
+
*
|
|
6
|
+
* There is NO transport method: the preview needs a `quote`-scoped Riddler
|
|
7
|
+
* credential, so only the Xochi Worker calls it, and the Worker proxy route is
|
|
8
|
+
* a follow-up owned by xochi#635. These shapes exist so that proxy and its
|
|
9
|
+
* consumers decode the solver's answer with the same schema the solver
|
|
10
|
+
* publishes (`packages/spec/wire/openapi.json`, `ShieldedPreviewResponse`).
|
|
11
|
+
*
|
|
12
|
+
* The preview is NOT a signing payload: `preview_digest` is the EIP-712 digest
|
|
13
|
+
* of `ShieldedPreviewV1` under the `Xochi Shielded Preview` domain, which no
|
|
14
|
+
* `ShieldedTermsV1` signature verifies against (`@xochi/spec`
|
|
15
|
+
* `conformance/shielded-terms.json`, PROPOSED). `executable` is always false.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.ShieldedPreviewResponseSchema = exports.ShieldedPreviewSchema = exports.ShieldedFeeBreakdownSchema = exports.ShieldedCorridorSchema = exports.ShieldedNetworkIdentitySchema = exports.aztecValue = void 0;
|
|
19
|
+
exports.decodeShieldedPreview = decodeShieldedPreview;
|
|
20
|
+
const zod_1 = require("zod");
|
|
21
|
+
const schemas_js_1 = require("./schemas.js");
|
|
22
|
+
/** 32-byte hex (`0x` + 64). */
|
|
23
|
+
const bytes32 = zod_1.z.string().regex(/^0x[0-9a-fA-F]{64}$/);
|
|
24
|
+
/**
|
|
25
|
+
* A 32-byte Aztec address or field element. The decoder checks the shape only;
|
|
26
|
+
* the solver has already refused zero and values at or above the BN254 modulus.
|
|
27
|
+
* A 20-byte EVM address never fits.
|
|
28
|
+
*/
|
|
29
|
+
exports.aztecValue = bytes32;
|
|
30
|
+
/** Canonical unsigned decimal string (uint256 members). */
|
|
31
|
+
const uintString = zod_1.z.string().regex(/^(0|[1-9][0-9]*)$/);
|
|
32
|
+
/** Canonical positive base-unit amount. */
|
|
33
|
+
const baseUnits = zod_1.z.string().regex(/^[1-9][0-9]*$/);
|
|
34
|
+
exports.ShieldedNetworkIdentitySchema = zod_1.z.object({
|
|
35
|
+
aztec_chain_id: zod_1.z.string().regex(/^[1-9][0-9]*$/),
|
|
36
|
+
protocol_version: zod_1.z.string().regex(/^[1-9][0-9]*$/),
|
|
37
|
+
rollup_address: schemas_js_1.ethAddress,
|
|
38
|
+
l1_chain_id: zod_1.z.string().regex(/^[1-9][0-9]*$/),
|
|
39
|
+
genesis_or_network_digest: bytes32,
|
|
40
|
+
});
|
|
41
|
+
exports.ShieldedCorridorSchema = zod_1.z.object({
|
|
42
|
+
corridor_id: bytes32,
|
|
43
|
+
revision: zod_1.z.number().int().min(1).max(4294967295),
|
|
44
|
+
manifest_digest: bytes32,
|
|
45
|
+
});
|
|
46
|
+
exports.ShieldedFeeBreakdownSchema = zod_1.z.object({
|
|
47
|
+
gas_floor: uintString,
|
|
48
|
+
solver_fee_bps: zod_1.z.number().int().min(0).max(10000),
|
|
49
|
+
solver_fee: uintString,
|
|
50
|
+
price_impact_bps: zod_1.z.number().int().min(0).max(10000),
|
|
51
|
+
price_impact: uintString,
|
|
52
|
+
venue_fee_bps: zod_1.z.number().int().min(0).max(10000),
|
|
53
|
+
venue_fee: uintString,
|
|
54
|
+
routing_fee_bps: zod_1.z.number().int().min(0).max(10000),
|
|
55
|
+
routing_fee: uintString,
|
|
56
|
+
total_bps: zod_1.z.number().int().min(0).max(40000),
|
|
57
|
+
total_fee: uintString,
|
|
58
|
+
});
|
|
59
|
+
/** `ShieldedPreviewV1` in snake_case: the terms minus the per-execution members. */
|
|
60
|
+
exports.ShieldedPreviewSchema = zod_1.z.object({
|
|
61
|
+
protocol_version: zod_1.z.literal("xochi.shielded.preview.v1"),
|
|
62
|
+
environment: zod_1.z.string().min(1),
|
|
63
|
+
source: zod_1.z.object({
|
|
64
|
+
chain_id: zod_1.z.number().int().positive(),
|
|
65
|
+
token_address: schemas_js_1.ethAddress,
|
|
66
|
+
wallet: schemas_js_1.ethAddress,
|
|
67
|
+
collector: schemas_js_1.ethAddress,
|
|
68
|
+
amount: baseUnits,
|
|
69
|
+
}),
|
|
70
|
+
destination: zod_1.z.object({
|
|
71
|
+
settlement_type: zod_1.z.literal("shielded"),
|
|
72
|
+
privacy_level: zod_1.z.literal("private"),
|
|
73
|
+
disclosure_profile: zod_1.z.literal("recipient-private-public-amount-v1"),
|
|
74
|
+
rail: zod_1.z.literal("a_tracer"),
|
|
75
|
+
network: exports.ShieldedNetworkIdentitySchema,
|
|
76
|
+
corridor: exports.ShieldedCorridorSchema,
|
|
77
|
+
asset_id: zod_1.z.string().min(1),
|
|
78
|
+
token_address: exports.aztecValue,
|
|
79
|
+
recipient_binding_id: zod_1.z.string().min(1),
|
|
80
|
+
recipient_descriptor_digest: bytes32,
|
|
81
|
+
recipient_address: exports.aztecValue,
|
|
82
|
+
amount: baseUnits,
|
|
83
|
+
fee_payer: exports.aztecValue,
|
|
84
|
+
}),
|
|
85
|
+
fee_breakdown: exports.ShieldedFeeBreakdownSchema,
|
|
86
|
+
refund_address: schemas_js_1.ethAddress,
|
|
87
|
+
refund_amount: baseUnits,
|
|
88
|
+
collect_before: zod_1.z.number().int().nonnegative(),
|
|
89
|
+
settlement_service_deadline: zod_1.z.number().int().nonnegative(),
|
|
90
|
+
recipient_binding_digest: bytes32,
|
|
91
|
+
policy_revision: zod_1.z.number().int().nonnegative(),
|
|
92
|
+
});
|
|
93
|
+
exports.ShieldedPreviewResponseSchema = zod_1.z.object({
|
|
94
|
+
executable: zod_1.z.literal(false),
|
|
95
|
+
preview_version: zod_1.z.literal("xochi.shielded.preview.v1"),
|
|
96
|
+
preview_digest: bytes32,
|
|
97
|
+
recipient_binding_digest: bytes32,
|
|
98
|
+
domain: zod_1.z.object({
|
|
99
|
+
name: zod_1.z.literal("Xochi Shielded Preview"),
|
|
100
|
+
version: zod_1.z.literal("1"),
|
|
101
|
+
chain_id: zod_1.z.number().int().positive(),
|
|
102
|
+
salt: bytes32,
|
|
103
|
+
}),
|
|
104
|
+
preview: exports.ShieldedPreviewSchema,
|
|
105
|
+
manifest: zod_1.z.object({
|
|
106
|
+
manifest_digest: bytes32,
|
|
107
|
+
aztec_sdk_version: zod_1.z.string().min(1),
|
|
108
|
+
recipient_account_class_id: exports.aztecValue,
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Decode a preview response. Throws (zod) on any shape the solver does not
|
|
113
|
+
* publish, including an executable or terms-shaped answer, and on a fee
|
|
114
|
+
* breakdown whose totals disagree: `total_fee` must be `max(gas_floor, layers)` (the gas
|
|
115
|
+
* floor is a minimum fee, as in FeePolicy) and `total_bps` the four rates.
|
|
116
|
+
*/
|
|
117
|
+
function decodeShieldedPreview(body) {
|
|
118
|
+
const decoded = exports.ShieldedPreviewResponseSchema.parse(body);
|
|
119
|
+
const f = decoded.preview.fee_breakdown;
|
|
120
|
+
const layers = BigInt(f.solver_fee) + BigInt(f.price_impact) + BigInt(f.venue_fee) + BigInt(f.routing_fee);
|
|
121
|
+
const gasFloor = BigInt(f.gas_floor);
|
|
122
|
+
const totalFee = layers > gasFloor ? layers : gasFloor;
|
|
123
|
+
const totalBps = f.solver_fee_bps + f.price_impact_bps + f.venue_fee_bps + f.routing_fee_bps;
|
|
124
|
+
if (BigInt(f.total_fee) !== totalFee || f.total_bps !== totalBps) {
|
|
125
|
+
throw new Error("shielded preview fee_breakdown totals disagree with max(gas_floor, layers)");
|
|
126
|
+
}
|
|
127
|
+
return decoded;
|
|
128
|
+
}
|
|
@@ -156,6 +156,10 @@ function step(m, msg, fx) {
|
|
|
156
156
|
return m.amount ? startQuote(m, fx, msg.now, quoteOpts(m.amount)) : m;
|
|
157
157
|
case "SignRequested":
|
|
158
158
|
return beginSign(m, fx, msg.now);
|
|
159
|
+
case "CancelSignRequested":
|
|
160
|
+
if (m.intent.status !== "signing" || !m.sign)
|
|
161
|
+
return m;
|
|
162
|
+
return transition(abandonSign(m, fx), fx, "quoted", { error: "Signature cancelled" });
|
|
159
163
|
case "PullSignRequested":
|
|
160
164
|
return beginPullSign(m, fx, msg.now);
|
|
161
165
|
case "ResetRequested":
|
|
@@ -717,8 +721,9 @@ function onTimer(m, fx, timer, now) {
|
|
|
717
721
|
return resendBackstop(m, fx, now);
|
|
718
722
|
return m.poll && !m.poll.stopped ? pollTick(m, fx, true) : m;
|
|
719
723
|
case "signPrompt":
|
|
720
|
-
// A
|
|
721
|
-
|
|
724
|
+
// A wallet prompt that never resolved. The flow still owns the prompt;
|
|
725
|
+
// `signFlowFailed` makes any reply arriving after this timer inert.
|
|
726
|
+
return m.sign
|
|
722
727
|
? signFlowFailed(m, fx, {
|
|
723
728
|
rejected: false,
|
|
724
729
|
value: "wallet_prompt_timeout",
|
|
@@ -826,6 +831,10 @@ function beginSign(m, fx, now) {
|
|
|
826
831
|
chainId: next.ctx.wallet.chainId,
|
|
827
832
|
},
|
|
828
833
|
});
|
|
834
|
+
// A wallet request can be dismissed without rejecting (mobile deep links in
|
|
835
|
+
// particular). Bound every prompt, including signature 1, so the ceremony
|
|
836
|
+
// always returns to an actionable surface.
|
|
837
|
+
setTimer(fx, "signPrompt", SIGN_PROMPT_TIMEOUT_MS, false);
|
|
829
838
|
return { ...next, sign: { id, origin: "sign", stage: "intent", intentSignature: null } };
|
|
830
839
|
}
|
|
831
840
|
function beginPullSign(m, fx, now) {
|
|
@@ -926,8 +935,7 @@ function onSigned(m, fx, flowId, purpose, result, now) {
|
|
|
926
935
|
// open; its signature then belongs to no ceremony.
|
|
927
936
|
if (m.intent.status !== "signing")
|
|
928
937
|
return abandonSign(m, fx);
|
|
929
|
-
|
|
930
|
-
clearTimer(fx, "signPrompt");
|
|
938
|
+
clearTimer(fx, "signPrompt");
|
|
931
939
|
if (!result.ok)
|
|
932
940
|
return signFlowFailed(m, fx, result.error);
|
|
933
941
|
if (purpose === "pull") {
|
|
@@ -1210,7 +1218,7 @@ function endSignFlow(m, flow) {
|
|
|
1210
1218
|
* affordance, no prompt timer. Nothing signed so far can execute after this.
|
|
1211
1219
|
*/
|
|
1212
1220
|
function abandonSign(m, fx) {
|
|
1213
|
-
if (m.sign
|
|
1221
|
+
if (m.sign)
|
|
1214
1222
|
clearTimer(fx, "signPrompt");
|
|
1215
1223
|
if (awaitingResend(m))
|
|
1216
1224
|
clearTimer(fx, "execute");
|
|
@@ -1226,12 +1234,16 @@ function signFlowFailed(m, fx, report) {
|
|
|
1226
1234
|
const flow = m.sign;
|
|
1227
1235
|
if (!flow)
|
|
1228
1236
|
return m;
|
|
1229
|
-
|
|
1230
|
-
clearTimer(fx, "signPrompt");
|
|
1237
|
+
clearTimer(fx, "signPrompt");
|
|
1231
1238
|
const next = endSignFlow(m, flow);
|
|
1232
1239
|
if (flow.origin === "sign") {
|
|
1233
1240
|
if (report.rejected)
|
|
1234
1241
|
return transition(next, fx, "quoted", { error: "Signature rejected" });
|
|
1242
|
+
if (report.value === "wallet_prompt_timeout") {
|
|
1243
|
+
return transition(next, fx, "quoted", {
|
|
1244
|
+
error: "Couldn't reach the wallet. Press Confirm to try again.",
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1235
1247
|
log(fx, "error", `${LOG} Sign error:`, report.detail);
|
|
1236
1248
|
return transition(next, fx, "failed", { error: report.value ?? "Signing failed" });
|
|
1237
1249
|
}
|
package/dist/esm/shared/index.js
CHANGED