@rhinestone/sdk 0.0.0-dev-20261008160531 → 0.0.0-dev-20261008160829
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/src/clients/orchestrator/client.js +1 -1
- package/dist/src/modules/validators/smart-sessions/settlement/lz.d.ts +8 -1
- package/dist/src/modules/validators/smart-sessions/settlement/lz.d.ts.map +1 -1
- package/dist/src/modules/validators/smart-sessions/settlement/lz.js +74 -44
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { createOrchestratorAuth } from './auth.js';
|
|
|
3
3
|
import { ChainCatalog, parseChains } from './chain-catalog.js';
|
|
4
4
|
import { fetchOrchestratorJson } from './fetch.js';
|
|
5
5
|
import { mapIntentRequestToWire, mapIntentStatusFromWire, mapPortfolioFromWire, mapQuoteResponseFromWire, mapSignedIntentToWire, mapSplitRequestToWire, mapSplitResultFromWire, } from './mappers.js';
|
|
6
|
-
const SDK_VERSION = '0.0.0-dev-
|
|
6
|
+
const SDK_VERSION = '0.0.0-dev-20261008160829';
|
|
7
7
|
const API_VERSION = '2026-04.blanc';
|
|
8
8
|
// Bounds how long a killed layer or pulled settlement address stays pinnable.
|
|
9
9
|
const CHAIN_CATALOG_TTL_MS = 10 * 60 * 1000;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScopedAction } from '../types.js';
|
|
1
|
+
import type { ScopedAction, UniversalActionPolicyParamRule } from '../types.js';
|
|
2
2
|
import type { SettlementContext } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* The most relay fee the key may send LayerZero's receiver, in USDC units: ~10x
|
|
@@ -6,6 +6,13 @@ import type { SettlementContext } from './types.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const LZ_CCTP_MAX_RELAY_FEE = 1000000n;
|
|
8
8
|
export declare const LZ_EXECUTE_SELECTOR: `0x${string}`;
|
|
9
|
+
type Rule = UniversalActionPolicyParamRule;
|
|
10
|
+
/**
|
|
11
|
+
* The same check on the same word. A usage-limited rule is never the same: one
|
|
12
|
+
* hoisted out of an OR would count on every call, not only when its branch runs.
|
|
13
|
+
*/
|
|
14
|
+
export declare const sameRule: (a: Rule, b: Rule) => boolean;
|
|
9
15
|
/** The execute call, pinned to the permit's destinations, recipients and cap. */
|
|
10
16
|
export declare function scopeLz(ctx: SettlementContext): ScopedAction;
|
|
17
|
+
export {};
|
|
11
18
|
//# sourceMappingURL=lz.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lz.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/smart-sessions/settlement/lz.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,YAAY,
|
|
1
|
+
{"version":3,"file":"lz.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/smart-sessions/settlement/lz.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,YAAY,EACZ,8BAA8B,EAC/B,MAAM,UAAU,CAAA;AAGjB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AA4BhD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,WAAa,CAAA;AAE/C,eAAO,MAAM,mBAAmB,eAE/B,CAAA;AAWD,KAAK,IAAI,GAAG,8BAA8B,CAAA;AAmB1C;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,YAKc,CAAA;AA2DvD,iFAAiF;AACjF,wBAAgB,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,YAAY,CAyP5D"}
|
|
@@ -8,12 +8,16 @@ import { SettlementLayerRefusal, served } from './served.js';
|
|
|
8
8
|
* quoteId)`, whose nested calls pull the tokens, bridge them and sweep the rest
|
|
9
9
|
* back. LZMultiCall runs any call it is handed, so every nested call is pinned:
|
|
10
10
|
* the array length, each element offset, target, data pointer and
|
|
11
|
-
* length-plus-selector, and each argument the key could redirect
|
|
12
|
-
*
|
|
11
|
+
* length-plus-selector, and each argument the key could redirect, except what
|
|
12
|
+
* LZMultiCall or the target already refuses (see `batch` and `pull`). The policy
|
|
13
|
+
* accepts two of the API's routes, in three layouts:
|
|
13
14
|
*
|
|
14
|
-
* - Stargate TAXI or BUS: delegateTransferFrom, approve(pool), pool.send, sweep.
|
|
15
15
|
* - CCTP: delegateTransferFrom, transfer(fee), approve(TokenMessengerV2),
|
|
16
16
|
* depositForBurn, sweep; to Plasma without the fee transfer.
|
|
17
|
+
* - Stargate TAXI: delegateTransferFrom, approve(pool), pool.send, sweep; only
|
|
18
|
+
* into a leg no CCTP route reaches. Where both exist CCTP is cheaper and
|
|
19
|
+
* faster, and the orchestrator must not plan Stargate. BUS, which the
|
|
20
|
+
* orchestrator does not plan, is refused.
|
|
17
21
|
*/
|
|
18
22
|
/**
|
|
19
23
|
* Circle's TokenMessengerV2, which the API's CCTP route approves and burns
|
|
@@ -33,6 +37,16 @@ const TRANSFER = toFunctionSelector('transfer(address,uint256)');
|
|
|
33
37
|
const DEPOSIT_FOR_BURN = toFunctionSelector('depositForBurn(uint256,uint32,bytes32,address,bytes32,uint256,uint32)');
|
|
34
38
|
const SWEEP = toFunctionSelector('sweep(address[],address)');
|
|
35
39
|
const ceil32 = (n) => BigInt(Math.ceil(n / 32) * 32);
|
|
40
|
+
const and = (terms) => terms.reduceRight((right, left) => ({ type: 'and', left, right }));
|
|
41
|
+
/**
|
|
42
|
+
* The same check on the same word. A usage-limited rule is never the same: one
|
|
43
|
+
* hoisted out of an OR would count on every call, not only when its branch runs.
|
|
44
|
+
*/
|
|
45
|
+
export const sameRule = (a, b) => a.usageLimit === undefined &&
|
|
46
|
+
b.usageLimit === undefined &&
|
|
47
|
+
a.condition === b.condition &&
|
|
48
|
+
a.calldataOffset === b.calldataOffset &&
|
|
49
|
+
BigInt(a.referenceValue) === BigInt(b.referenceValue);
|
|
36
50
|
/**
|
|
37
51
|
* The pins of an `execute` batch under the canonical encoding, below the
|
|
38
52
|
* `calls` pointer every route shares, and each call's argument accessor.
|
|
@@ -45,10 +59,17 @@ function batch(calls) {
|
|
|
45
59
|
const start = 0x60n + relative;
|
|
46
60
|
const data = start + 128n;
|
|
47
61
|
const at = (offset) => data + 4n + offset;
|
|
48
|
-
|
|
62
|
+
// No call's value is pinned: the send's is the messaging fee, and every
|
|
63
|
+
// other target reverts on value (a non-payable solc function, checked on
|
|
64
|
+
// every LZ source chain), which LZMultiCall bubbles.
|
|
65
|
+
rules.push(pinValue(0x60n + BigInt(i) * 32n, relative), pin(start, call.target), pinValue(start + 64n, 0x60n),
|
|
49
66
|
// The word ending at the selector: the length's low 28 bytes, then the
|
|
50
67
|
// selector. A length above 2^224 fails to decode, so this pins both.
|
|
51
|
-
|
|
68
|
+
...(call.shapeChecked
|
|
69
|
+
? []
|
|
70
|
+
: [
|
|
71
|
+
pinValue(data - 28n, (BigInt(call.length) << 32n) | BigInt(call.selector)),
|
|
72
|
+
]), ...call.args(at));
|
|
52
73
|
args.push(at);
|
|
53
74
|
relative += 128n + ceil32(call.length);
|
|
54
75
|
});
|
|
@@ -70,15 +91,15 @@ export function scopeLz(ctx) {
|
|
|
70
91
|
const cap = (offset) => ctx.cap === undefined ? [] : [cumulativeCap(offset, ctx.cap)];
|
|
71
92
|
const crossChain = (leg) => leg.chainId !== ctx.chainId;
|
|
72
93
|
const recipient = (offset, leg) => leg.recipient === undefined ? [] : [pinWord(offset, pad(leg.recipient))];
|
|
94
|
+
// LZMultiCall routes a call to its own TransferDelegate through
|
|
95
|
+
// `_handleTransfer`, which reverts unless the data is 132 bytes of
|
|
96
|
+
// `delegateTransferFrom` pulling from the caller, the account.
|
|
73
97
|
const pull = {
|
|
74
98
|
target: transferDelegate,
|
|
75
99
|
selector: DELEGATE_TRANSFER_FROM,
|
|
76
100
|
length: 132,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pin(at(32n), account),
|
|
80
|
-
pin(at(64n), multiCall),
|
|
81
|
-
],
|
|
101
|
+
shapeChecked: true,
|
|
102
|
+
args: (at) => [pin(at(0n), token), pin(at(64n), multiCall)],
|
|
82
103
|
};
|
|
83
104
|
const sweep = {
|
|
84
105
|
target: multiCall,
|
|
@@ -99,13 +120,22 @@ export function scopeLz(ctx) {
|
|
|
99
120
|
args: (at) => [pin(at(0n), spender)],
|
|
100
121
|
});
|
|
101
122
|
const routes = [];
|
|
123
|
+
const cctp = source.cctp;
|
|
124
|
+
const cctpFrom = cctp !== undefined && isAddressEqual(token, cctp.token);
|
|
125
|
+
// A route exists where both chains carry its block.
|
|
126
|
+
const cctpLinks = (leg) => cctpFrom && crossChain(leg) && servedLz(leg)?.cctp !== undefined;
|
|
127
|
+
// A leg whose token CCTP does not mint, such as a Stargate USDC, stays
|
|
128
|
+
// Stargate's.
|
|
129
|
+
const cctpReaches = (leg) => {
|
|
130
|
+
const dst = servedLz(leg)?.cctp;
|
|
131
|
+
return cctpLinks(leg) && !!dst && isAddressEqual(leg.token, dst.token);
|
|
132
|
+
};
|
|
102
133
|
const stargate = source.stargateUsdc;
|
|
103
134
|
if (stargate !== undefined && isAddressEqual(token, stargate.token)) {
|
|
104
135
|
const send = {
|
|
105
136
|
target: stargate.pool,
|
|
106
137
|
selector: OFT_SEND_SELECTOR,
|
|
107
138
|
length: 484,
|
|
108
|
-
payable: true,
|
|
109
139
|
args: (at) => [
|
|
110
140
|
pinValue(at(SEND.sendParamPointer), 0x80n),
|
|
111
141
|
// The fee is native only; the refund returns to LZMultiCall, whose
|
|
@@ -124,28 +154,15 @@ export function scopeLz(ctx) {
|
|
|
124
154
|
const calls = [pull, approve(stargate.pool), send, sweep];
|
|
125
155
|
const { rules, args } = batch(calls);
|
|
126
156
|
const s = args[2];
|
|
127
|
-
// TAXI: extraOptions 0x0003, composeMsg and oftCmd empty.
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
pinValue(s(0x180n), 0x0003n << 240n),
|
|
134
|
-
pinValue(s(0x1a0n), 0n),
|
|
135
|
-
pinValue(s(0x1c0n), 0n),
|
|
136
|
-
];
|
|
137
|
-
const bus = [
|
|
138
|
-
pinValue(s(SEND.composeMsgPointer), 0x100n),
|
|
139
|
-
pinValue(s(SEND.oftCmdPointer), 0x120n),
|
|
140
|
-
pinValue(s(0x160n), 0n),
|
|
141
|
-
pinValue(s(0x180n), 0n),
|
|
142
|
-
pinValue(s(0x1a0n), 1n),
|
|
143
|
-
pinValue(s(0x1c0n), 0x01n << 248n),
|
|
144
|
-
];
|
|
145
|
-
const reaches = (leg) => crossChain(leg) && servedLz(leg)?.stargateUsdc !== undefined;
|
|
157
|
+
// TAXI: extraOptions 0x0003, composeMsg and oftCmd empty. No native drop,
|
|
158
|
+
// no compose.
|
|
159
|
+
rules.push(pinValue(s(SEND.composeMsgPointer), 0x120n), pinValue(s(SEND.oftCmdPointer), 0x140n), pinValue(s(0x160n), 2n), pinValue(s(0x180n), 0x0003n << 240n), pinValue(s(0x1a0n), 0n), pinValue(s(0x1c0n), 0n));
|
|
160
|
+
const reaches = (leg) => crossChain(leg) &&
|
|
161
|
+
servedLz(leg)?.stargateUsdc !== undefined &&
|
|
162
|
+
!cctpReaches(leg);
|
|
146
163
|
routes.push({
|
|
147
164
|
rules,
|
|
148
|
-
|
|
165
|
+
calls: calls.length,
|
|
149
166
|
reaches,
|
|
150
167
|
leg: (leg) => {
|
|
151
168
|
const dst = servedLz(leg)?.stargateUsdc;
|
|
@@ -159,14 +176,15 @@ export function scopeLz(ctx) {
|
|
|
159
176
|
limits: cap(args[0](96n)),
|
|
160
177
|
});
|
|
161
178
|
}
|
|
162
|
-
|
|
163
|
-
if (cctp !== undefined && isAddressEqual(token, cctp.token)) {
|
|
179
|
+
if (cctpFrom) {
|
|
164
180
|
const burn = {
|
|
165
181
|
target: LZ_CCTP_TOKEN_MESSENGER,
|
|
166
182
|
selector: DEPOSIT_FOR_BURN,
|
|
167
183
|
length: 228,
|
|
184
|
+
// `burnToken` is not pinned: the pull and the sweep are pinned to this
|
|
185
|
+
// token and the account, so another burn token can only spend what a
|
|
186
|
+
// third party left in LZMultiCall, and the pull is swept back.
|
|
168
187
|
args: (at) => [
|
|
169
|
-
pin(at(96n), token),
|
|
170
188
|
// A non-zero destinationCaller restricts who may mint; the API never
|
|
171
189
|
// sets one.
|
|
172
190
|
pinValue(at(128n), 0n),
|
|
@@ -187,11 +205,10 @@ export function scopeLz(ctx) {
|
|
|
187
205
|
: [pull, fee, approve(LZ_CCTP_TOKEN_MESSENGER), burn, sweep];
|
|
188
206
|
const { rules, args } = batch(calls);
|
|
189
207
|
const b = args[calls.indexOf(burn)];
|
|
190
|
-
const reaches = (leg) =>
|
|
191
|
-
servedLz(leg)?.cctp !== undefined &&
|
|
192
|
-
(servedLz(leg)?.cctp?.feeless === true) === feeless;
|
|
208
|
+
const reaches = (leg) => cctpLinks(leg) && (servedLz(leg)?.cctp?.feeless === true) === feeless;
|
|
193
209
|
routes.push({
|
|
194
210
|
rules,
|
|
211
|
+
calls: calls.length,
|
|
195
212
|
reaches,
|
|
196
213
|
leg: (leg) => {
|
|
197
214
|
const dst = servedLz(leg)?.cctp;
|
|
@@ -230,17 +247,30 @@ export function scopeLz(ctx) {
|
|
|
230
247
|
if (pinned.length === 0)
|
|
231
248
|
return [];
|
|
232
249
|
return [
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
250
|
+
{
|
|
251
|
+
route,
|
|
252
|
+
rest: [
|
|
253
|
+
anyOf(pinned.map(allOf)),
|
|
254
|
+
...(route.limits.length ? [allOf(route.limits)] : []),
|
|
255
|
+
],
|
|
256
|
+
},
|
|
239
257
|
];
|
|
240
258
|
});
|
|
241
259
|
if (branches.length === 0) {
|
|
242
260
|
throw new SettlementLayerRefusal(`crossChainPermits: LZ has no route from chain ${ctx.chainId} to any \`to\` chain`);
|
|
243
261
|
}
|
|
262
|
+
// A pure pin that every branch of a group carries (`group.every`) is checked
|
|
263
|
+
// once, ahead of their OR; that alone makes it sound. Grouping by call count
|
|
264
|
+
// only picks the batches likely to share pins.
|
|
265
|
+
const groups = [...new Set(branches.map(({ route }) => route.calls))].map((calls) => {
|
|
266
|
+
const group = branches.filter(({ route }) => route.calls === calls);
|
|
267
|
+
const shared = group[0].route.rules.filter((rule) => group.every(({ route }) => route.rules.some((r) => sameRule(r, rule))));
|
|
268
|
+
const own = group.map(({ route, rest }) => {
|
|
269
|
+
const left = route.rules.filter((r) => !shared.some((s) => sameRule(s, r)));
|
|
270
|
+
return and([...(left.length ? [allOf(left)] : []), ...rest]);
|
|
271
|
+
});
|
|
272
|
+
return and([...(shared.length ? [allOf(shared)] : []), anyOf(own)]);
|
|
273
|
+
});
|
|
244
274
|
return {
|
|
245
275
|
target: multiCall,
|
|
246
276
|
selector: LZ_EXECUTE_SELECTOR,
|
|
@@ -258,7 +288,7 @@ export function scopeLz(ctx) {
|
|
|
258
288
|
// stale TransferDelegate allowance could fund a second route, and a
|
|
259
289
|
// second Stargate send would pay another native fee.
|
|
260
290
|
left: allOf([{ ...pinValue(0n, 0x40n), usageLimit: 0x40n }]),
|
|
261
|
-
right: anyOf(
|
|
291
|
+
right: anyOf(groups),
|
|
262
292
|
},
|
|
263
293
|
},
|
|
264
294
|
],
|