@rhinestone/sdk 2.5.0 → 2.7.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/src/accounts/adapters/kernel.d.ts +0 -1
- package/dist/src/accounts/adapters/kernel.d.ts.map +1 -1
- package/dist/src/accounts/adapters/kernel.js +1 -13
- package/dist/src/accounts/kernel-signing.d.ts +3 -0
- package/dist/src/accounts/kernel-signing.d.ts.map +1 -0
- package/dist/src/accounts/kernel-signing.js +13 -0
- package/dist/src/api/account.d.ts.map +1 -1
- package/dist/src/api/account.js +118 -0
- package/dist/src/api/compose-types.d.ts +7 -0
- package/dist/src/api/compose-types.d.ts.map +1 -1
- package/dist/src/api/direct-signing.d.ts.map +1 -1
- package/dist/src/api/direct-signing.js +11 -6
- package/dist/src/api/signer-selection.js +11 -0
- package/dist/src/clients/orchestrator/client.js +1 -1
- package/dist/src/clients/orchestrator/public.d.ts +8 -1
- package/dist/src/clients/orchestrator/public.d.ts.map +1 -1
- package/dist/src/clients/orchestrator/types.d.ts +6 -0
- package/dist/src/clients/orchestrator/types.d.ts.map +1 -1
- package/dist/src/config/account.d.ts +152 -7
- package/dist/src/config/account.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/modules/validators/capabilities.d.ts.map +1 -1
- package/dist/src/modules/validators/capabilities.js +33 -10
- package/dist/src/modules/validators/contribution.d.ts.map +1 -1
- package/dist/src/modules/validators/contribution.js +12 -0
- package/dist/src/modules/validators/definition.d.ts.map +1 -1
- package/dist/src/modules/validators/definition.js +18 -0
- package/dist/src/modules/validators/permissions.d.ts +13 -0
- package/dist/src/modules/validators/permissions.d.ts.map +1 -1
- package/dist/src/modules/validators/permissions.js +140 -2
- package/dist/src/modules/validators/quorum.d.ts +66 -0
- package/dist/src/modules/validators/quorum.d.ts.map +1 -0
- package/dist/src/modules/validators/quorum.js +231 -0
- package/dist/src/modules/validators/resolve.d.ts.map +1 -1
- package/dist/src/modules/validators/resolve.js +5 -0
- package/dist/src/modules/validators/smart-sessions/resolve.d.ts.map +1 -1
- package/dist/src/modules/validators/smart-sessions/resolve.js +112 -13
- package/dist/src/modules/validators/smart-sessions/swap/fynd.d.ts +92 -0
- package/dist/src/modules/validators/smart-sessions/swap/fynd.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/fynd.js +101 -0
- package/dist/src/modules/validators/smart-sessions/swap/rhinestone.d.ts +147 -0
- package/dist/src/modules/validators/smart-sessions/swap/rhinestone.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/rhinestone.js +305 -0
- package/dist/src/modules/validators/smart-sessions/swap/rules.d.ts +73 -0
- package/dist/src/modules/validators/smart-sessions/swap/rules.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/rules.js +105 -0
- package/dist/src/modules/validators/smart-sessions/swap/scope.d.ts +31 -0
- package/dist/src/modules/validators/smart-sessions/swap/scope.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/scope.js +168 -0
- package/dist/src/modules/validators/smart-sessions/swap/zero-ex.d.ts +143 -0
- package/dist/src/modules/validators/smart-sessions/swap/zero-ex.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/zero-ex.js +164 -0
- package/dist/src/modules/validators/smart-sessions/types.d.ts +78 -0
- package/dist/src/modules/validators/smart-sessions/types.d.ts.map +1 -1
- package/dist/src/modules/validators/types.d.ts +26 -3
- package/dist/src/modules/validators/types.d.ts.map +1 -1
- package/dist/src/signing/hash.d.ts +9 -0
- package/dist/src/signing/hash.d.ts.map +1 -0
- package/dist/src/signing/hash.js +15 -0
- package/dist/src/signing/intent-plans/assemble.d.ts.map +1 -1
- package/dist/src/signing/intent-plans/assemble.js +10 -0
- package/dist/src/signing/intent-plans/independent.d.ts.map +1 -1
- package/dist/src/signing/intent-plans/independent.js +5 -1
- package/dist/src/signing/plan.d.ts.map +1 -1
- package/dist/src/signing/plan.js +14 -1
- package/dist/src/signing/quorum.d.ts +77 -0
- package/dist/src/signing/quorum.d.ts.map +1 -0
- package/dist/src/signing/quorum.js +52 -0
- package/dist/src/signing/signers/ecdsa.d.ts +1 -1
- package/dist/src/signing/signers/ecdsa.d.ts.map +1 -1
- package/dist/src/signing/signers/ecdsa.js +8 -0
- package/dist/src/signing/typed-data.d.ts +12 -6
- package/dist/src/signing/typed-data.d.ts.map +1 -1
- package/dist/src/signing/typed-data.js +9 -4
- package/dist/src/signing/types.d.ts +8 -0
- package/dist/src/signing/types.d.ts.map +1 -1
- package/dist/src/smart-sessions/index.d.ts +10 -5
- package/dist/src/smart-sessions/index.d.ts.map +1 -1
- package/dist/src/smart-sessions/index.js +6 -1
- package/dist/src/transactions/intents/sign-transaction.d.ts.map +1 -1
- package/dist/src/transactions/intents/sign-transaction.js +161 -47
- package/dist/src/transactions/user-operations/prepare.d.ts.map +1 -1
- package/dist/src/transactions/user-operations/prepare.js +3 -1
- package/dist/src/transactions/user-operations/validator-account.d.ts.map +1 -1
- package/dist/src/transactions/user-operations/validator-account.js +4 -0
- package/package.json +5 -1
|
@@ -9,6 +9,7 @@ import { DEFAULT_POLICY_ADDRESSES, resolvePolicyAddresses, } from './policies/ad
|
|
|
9
9
|
import { expandCrossChainPermit, resolvePermit2ClaimPolicy, } from './policies/claim.js';
|
|
10
10
|
import { encodeSessionPolicy } from './policies/encode.js';
|
|
11
11
|
import { resolveSessionSigning } from './signing.js';
|
|
12
|
+
import { resolveSwapScope } from './swap/scope.js';
|
|
12
13
|
export const SMART_SESSIONS_FALLBACK_TARGET_FLAG = '0x0000000000000000000000000000000000000001';
|
|
13
14
|
export const SMART_SESSIONS_FALLBACK_TARGET_SELECTOR_FLAG = '0x00000001';
|
|
14
15
|
export const SMART_SESSIONS_FALLBACK_TARGET_SELECTOR_FLAG_PERMITTED_TO_CALL_SMARTSESSION = '0x00000002';
|
|
@@ -31,16 +32,73 @@ export function resolveSessionData(definition, options = {}) {
|
|
|
31
32
|
actionTarget: SMART_SESSIONS_FALLBACK_TARGET_FLAG,
|
|
32
33
|
actionPolicies: [{ policy: addresses.sudo, initData: '0x' }],
|
|
33
34
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// `swap` is sugar over permissions + actions: it compiles to one merged
|
|
36
|
+
// approve plus one scoped swap action per venue, then rides the same
|
|
37
|
+
// resolution path. Venue routers, selectors and calldata offsets stay inside
|
|
38
|
+
// swap-venues.ts so they never reach the public surface (RHI-6286).
|
|
39
|
+
const swapScope = definition.swap
|
|
40
|
+
? resolveSwapScope(definition.swap, definition.chain.id, environment)
|
|
41
|
+
: undefined;
|
|
42
|
+
// Declaring `swap` IS the restriction — a swap-scoped session that still
|
|
43
|
+
// carried the wildcard fallback would let the session key call anything the
|
|
44
|
+
// global intent-execution whitelist allows, which is the opposite of what the
|
|
45
|
+
// caller asked for. `restrictToActions` stays as the explicit spelling for
|
|
46
|
+
// sessions scoped by hand.
|
|
47
|
+
const restricted = definition.restrictToActions === true || swapScope !== undefined;
|
|
48
|
+
const permissions = [
|
|
49
|
+
...(definition.permissions ?? []),
|
|
50
|
+
...(swapScope?.permissions ?? []),
|
|
51
|
+
];
|
|
52
|
+
const userActions = permissions.length ? resolvePermissions(permissions) : [];
|
|
53
|
+
// Raw scoped actions (target + selector + policies) for calls that can't be
|
|
54
|
+
// addressed by the ABI-name `permissions` sugar — e.g. a fynd swap scoped by
|
|
55
|
+
// its raw selector with no ABI (RHI-6286).
|
|
56
|
+
const rawActions = [
|
|
57
|
+
...(definition.actions ?? []),
|
|
58
|
+
...(swapScope?.actions ?? []),
|
|
59
|
+
];
|
|
60
|
+
// A restricted session drops the fallback action, which is also where a
|
|
61
|
+
// cross-chain permit's spending-limit / time-frame guardrails live — so a
|
|
62
|
+
// restricted session combined with a permit would keep claim signing but lose
|
|
63
|
+
// maxAmount/deadline enforcement. These are different authorization surfaces;
|
|
64
|
+
// reject the combination rather than silently drop the guardrails.
|
|
65
|
+
if (restricted &&
|
|
66
|
+
(definition.crossChainPermits?.length || definition.claimPolicies?.length)) {
|
|
67
|
+
throw new Error('restrictToActions is incompatible with crossChainPermits/claimPolicies: ' +
|
|
68
|
+
'dropping the fallback also drops the permit guardrails (spending/time ' +
|
|
69
|
+
'limits). Use a restricted scoped-action session or a permit session, ' +
|
|
70
|
+
'not both.');
|
|
71
|
+
}
|
|
72
|
+
// Guard raw actions from reintroducing the wildcard: reject one without
|
|
73
|
+
// target+selector (would map to the fallback flags), or one that targets the
|
|
74
|
+
// fallback sentinel outright — either would re-add the wildcard action that
|
|
75
|
+
// restrictToActions drops.
|
|
76
|
+
for (const a of rawActions) {
|
|
77
|
+
if (!('target' in a) || !('selector' in a)) {
|
|
78
|
+
throw new Error('definition.actions entries must be scoped (target + selector); a ' +
|
|
79
|
+
'fallback-shaped action would map to the wildcard fallback target');
|
|
80
|
+
}
|
|
81
|
+
if (a.target.toLowerCase() ===
|
|
82
|
+
SMART_SESSIONS_FALLBACK_TARGET_FLAG.toLowerCase()) {
|
|
83
|
+
throw new Error('definition.actions must not target the fallback sentinel ' +
|
|
84
|
+
`(${SMART_SESSIONS_FALLBACK_TARGET_FLAG}) — it reintroduces the wildcard action`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
37
87
|
const expandedPermits = (definition.crossChainPermits ?? []).map((input) => expandCrossChainPermit(resolveCrossChainPermission(input), environment));
|
|
38
88
|
const permitFallbackPolicies = expandedPermits.flatMap(({ fallbackPolicies }) => fallbackPolicies);
|
|
89
|
+
// The wildcard intent-execution fallback. Dropped for a restricted session so
|
|
90
|
+
// the explicit permissions are the ONLY authorized ops — a non-listed selector
|
|
91
|
+
// then reverts instead of escaping via the global intent-execution target
|
|
92
|
+
// whitelist (RHI-6286).
|
|
93
|
+
const fallbackAction = {
|
|
94
|
+
policies: [{ type: 'intent-execution' }, ...permitFallbackPolicies],
|
|
95
|
+
};
|
|
39
96
|
const injectedActions = [
|
|
40
97
|
// Native-wrap `deposit()` is only permitted when the caller supplies the
|
|
41
98
|
// chain's wrapped-native token (e.g. via `RhinestoneSDK.createSession`,
|
|
42
|
-
// which resolves it from `/chains`).
|
|
43
|
-
|
|
99
|
+
// which resolves it from `/chains`). Dropped for a restricted session so it
|
|
100
|
+
// can't add an unrequested sudo action beyond the caller's permissions.
|
|
101
|
+
...(options.wrappedNativeToken && !restricted
|
|
44
102
|
? [
|
|
45
103
|
{
|
|
46
104
|
target: options.wrappedNativeToken,
|
|
@@ -54,17 +112,45 @@ export function resolveSessionData(definition, options = {}) {
|
|
|
54
112
|
},
|
|
55
113
|
]
|
|
56
114
|
: []),
|
|
57
|
-
|
|
58
|
-
policies: [{ type: 'intent-execution' }, ...permitFallbackPolicies],
|
|
59
|
-
},
|
|
115
|
+
...(restricted ? [] : [fallbackAction]),
|
|
60
116
|
{
|
|
61
117
|
target: DUMMY_PRECLAIMOP_TARGET,
|
|
62
118
|
selector: DUMMY_PRECLAIMOP_SELECTOR,
|
|
63
|
-
|
|
119
|
+
// The real pre-claim op carries no value, so cap it for a restricted
|
|
120
|
+
// session rather than granting sudo, which would let this injected action
|
|
121
|
+
// send native value to the dummy target.
|
|
122
|
+
//
|
|
123
|
+
// 1 wei, NOT 0: `ValueLimitPolicy.initializeWithMultiplexer` does
|
|
124
|
+
// `require(valueLimit != 0)`, so a zero limit reverts while the policy is
|
|
125
|
+
// being installed. That made every restricted session impossible to
|
|
126
|
+
// enable — the revert surfaces as `InvalidSignature()` from the emissary,
|
|
127
|
+
// which reads as a signature problem rather than a policy-init one.
|
|
128
|
+
// 1 wei is the smallest limit that installs, and the op carries no value.
|
|
129
|
+
policies: restricted
|
|
130
|
+
? [{ type: 'value-limit', limit: 1n }]
|
|
131
|
+
: [{ type: 'sudo' }],
|
|
64
132
|
},
|
|
65
133
|
];
|
|
66
|
-
|
|
67
|
-
|
|
134
|
+
if (restricted && !userActions.length && !rawActions.length) {
|
|
135
|
+
throw new Error('restrictToActions drops the fallback, so the session must supply at ' +
|
|
136
|
+
'least one permission or action — none were given');
|
|
137
|
+
}
|
|
138
|
+
// Raw actions bypass resolvePermissions' duplicate guard, so a raw action that
|
|
139
|
+
// collides with an ABI permission (or another raw action) on the same
|
|
140
|
+
// (target, selector) would map to the same on-chain action id and silently
|
|
141
|
+
// overwrite policy config — reject it instead.
|
|
142
|
+
const scoped = [...userActions, ...rawActions].filter((a) => 'target' in a && 'selector' in a);
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
for (const a of scoped) {
|
|
145
|
+
const key = `${a.target.toLowerCase()}:${a.selector.toLowerCase()}`;
|
|
146
|
+
if (seen.has(key)) {
|
|
147
|
+
throw new Error(`Duplicate scoped action for (${a.target}, ${a.selector}) — permissions ` +
|
|
148
|
+
'and actions share one on-chain action id; merge them into a single entry');
|
|
149
|
+
}
|
|
150
|
+
seen.add(key);
|
|
151
|
+
}
|
|
152
|
+
const actions = userActions.length || rawActions.length || permitFallbackPolicies.length
|
|
153
|
+
? [...userActions, ...rawActions, ...injectedActions].map((action) => ({
|
|
68
154
|
actionTargetSelector: 'selector' in action
|
|
69
155
|
? action.selector
|
|
70
156
|
: SMART_SESSIONS_FALLBACK_TARGET_SELECTOR_FLAG,
|
|
@@ -81,8 +167,12 @@ export function resolveSessionData(definition, options = {}) {
|
|
|
81
167
|
policy: PERMIT2_CLAIM_POLICY_ADDRESS,
|
|
82
168
|
initData: encodePermit2ClaimPolicyInitData(resolvePermit2ClaimPolicy(policy)),
|
|
83
169
|
}));
|
|
170
|
+
// A restricted session must not leave an open ERC-1271 signing surface: with
|
|
171
|
+
// signing defaulting to unrestricted, a session key limited to swap/approve
|
|
172
|
+
// could still sign e.g. a Permit2 approval off-chain and move funds. Default it
|
|
173
|
+
// to `disabled` when restricting; the caller can still opt into a signing policy.
|
|
84
174
|
const erc7739Policies = resolveSessionSigning({
|
|
85
|
-
signing: definition.signing,
|
|
175
|
+
signing: definition.signing ?? (restricted ? { mode: 'disabled' } : undefined),
|
|
86
176
|
environment,
|
|
87
177
|
addresses,
|
|
88
178
|
});
|
|
@@ -108,7 +198,15 @@ export function toSession(definition, options = {}) {
|
|
|
108
198
|
return {
|
|
109
199
|
chain: definition.chain,
|
|
110
200
|
owners: definition.owners,
|
|
111
|
-
|
|
201
|
+
// Drives `verifyExecutions`, which selects the signature mode: false lets an
|
|
202
|
+
// already-enabled session sign in pure ERC-1271 mode, and that mode never
|
|
203
|
+
// reaches the emissary's `verifyExecution` — so the action policies are
|
|
204
|
+
// never consulted. `swap` compiles to actions, so it MUST count here, or a
|
|
205
|
+
// swap-only session would silently stop being action-checked the moment it
|
|
206
|
+
// is enabled.
|
|
207
|
+
hasExplicitPermissions: Boolean(definition.permissions?.length ||
|
|
208
|
+
definition.actions?.length ||
|
|
209
|
+
definition.swap),
|
|
112
210
|
permissionId: getPermissionIdFromData(data),
|
|
113
211
|
sessionValidator: data.sessionValidator,
|
|
114
212
|
sessionValidatorInitData: data.sessionValidatorInitData,
|
|
@@ -116,6 +214,7 @@ export function toSession(definition, options = {}) {
|
|
|
116
214
|
erc7739Policies: data.erc7739Policies,
|
|
117
215
|
actions: data.actions,
|
|
118
216
|
claimPolicies: [...(definition.claimPolicies ?? []), ...expandedClaims],
|
|
217
|
+
...(definition.swap ? { swap: definition.swap } : {}),
|
|
119
218
|
};
|
|
120
219
|
}
|
|
121
220
|
export { DEFAULT_POLICY_ADDRESSES };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
|
+
import type { FyndVenue } from '../types.js';
|
|
3
|
+
import type { VenueContext, VenueScoping } from './rules.js';
|
|
4
|
+
/**
|
|
5
|
+
* fynd — Rhinestone's self-hosted Tycho aggregator.
|
|
6
|
+
*
|
|
7
|
+
* Unlike 0x there is no allowance-holder indirection: the TychoRouter is both
|
|
8
|
+
* the swap target and the ERC-20 approval spender, and every field we care about
|
|
9
|
+
* is a named static argument in the calldata head. That means nothing here is a
|
|
10
|
+
* magic offset — the rules are addressed by ABI parameter name.
|
|
11
|
+
*/
|
|
12
|
+
/** Chains with a deployed, whitelisted TychoRouter. */
|
|
13
|
+
export declare const FYND_CHAIN_IDS: readonly [1, 56, 130, 137, 8453, 9745, 42161];
|
|
14
|
+
export type FyndChainId = (typeof FYND_CHAIN_IDS)[number];
|
|
15
|
+
/**
|
|
16
|
+
* TychoRouter per chain — the contract fynd's encoded fills target.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors `fynd.routers` in the orchestrator's quoters.jsonnet. A chain is
|
|
19
|
+
* listed only when the router is whitelisted in `IntentExecutionPolicy`, since a
|
|
20
|
+
* quoter enabled without a whitelisted target produces quotes that revert.
|
|
21
|
+
*/
|
|
22
|
+
export declare const FYND_ROUTERS: Record<FyndChainId, Address>;
|
|
23
|
+
/**
|
|
24
|
+
* TychoRouter `singleSwap`, the entrypoint fynd's encoded quotes call.
|
|
25
|
+
*
|
|
26
|
+
* Only the argument TYPES determine the selector and the head layout, and those
|
|
27
|
+
* are pinned by {@link FYND_SWAP_SELECTOR}'s assertion in the test suite. The
|
|
28
|
+
* NAMES of the first five are corroborated by observed fynd calldata; the
|
|
29
|
+
* trailing tuple's component names are our own labels and carry no guarantee —
|
|
30
|
+
* we never address them, since a tuple containing `bytes` is dynamic and sits
|
|
31
|
+
* behind a pointer.
|
|
32
|
+
*/
|
|
33
|
+
export declare const tychoRouterAbi: readonly [{
|
|
34
|
+
readonly type: "function";
|
|
35
|
+
readonly name: "singleSwap";
|
|
36
|
+
readonly stateMutability: "payable";
|
|
37
|
+
readonly inputs: readonly [{
|
|
38
|
+
readonly name: "amountIn";
|
|
39
|
+
readonly type: "uint256";
|
|
40
|
+
}, {
|
|
41
|
+
readonly name: "tokenIn";
|
|
42
|
+
readonly type: "address";
|
|
43
|
+
}, {
|
|
44
|
+
readonly name: "tokenOut";
|
|
45
|
+
readonly type: "address";
|
|
46
|
+
}, {
|
|
47
|
+
readonly name: "minAmountOut";
|
|
48
|
+
readonly type: "uint256";
|
|
49
|
+
}, {
|
|
50
|
+
readonly name: "receiver";
|
|
51
|
+
readonly type: "address";
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "permit";
|
|
54
|
+
readonly type: "tuple";
|
|
55
|
+
readonly components: readonly [{
|
|
56
|
+
readonly name: "nonce";
|
|
57
|
+
readonly type: "uint16";
|
|
58
|
+
}, {
|
|
59
|
+
readonly name: "token";
|
|
60
|
+
readonly type: "address";
|
|
61
|
+
}, {
|
|
62
|
+
readonly name: "amount";
|
|
63
|
+
readonly type: "uint256";
|
|
64
|
+
}, {
|
|
65
|
+
readonly name: "deadline";
|
|
66
|
+
readonly type: "uint256";
|
|
67
|
+
}, {
|
|
68
|
+
readonly name: "signature";
|
|
69
|
+
readonly type: "bytes";
|
|
70
|
+
}];
|
|
71
|
+
}, {
|
|
72
|
+
readonly name: "swap";
|
|
73
|
+
readonly type: "bytes";
|
|
74
|
+
}];
|
|
75
|
+
readonly outputs: readonly [{
|
|
76
|
+
readonly name: "amountOut";
|
|
77
|
+
readonly type: "uint256";
|
|
78
|
+
}];
|
|
79
|
+
}];
|
|
80
|
+
export declare const FYND_SWAP_SELECTOR: `0x${string}`;
|
|
81
|
+
/**
|
|
82
|
+
* Scope a session to fynd swaps.
|
|
83
|
+
*
|
|
84
|
+
* Needs no addresses: the router is a per-chain deployment the SDK knows, and
|
|
85
|
+
* because it is also the approval spender there is no separate allowance target
|
|
86
|
+
* to reason about.
|
|
87
|
+
*/
|
|
88
|
+
export declare function fynd(options?: {
|
|
89
|
+
maxSpend?: bigint;
|
|
90
|
+
}): FyndVenue;
|
|
91
|
+
export declare function scopeFynd(ctx: VenueContext): VenueScoping;
|
|
92
|
+
//# sourceMappingURL=fynd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fynd.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/smart-sessions/swap/fynd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAsB,MAAM,MAAM,CAAA;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAkC,MAAM,UAAU,CAAA;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAGzD;;;;;;;GAOG;AAEH,uDAAuD;AACvD,eAAO,MAAM,cAAc,+CAAgD,CAAA;AAE3E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAQrD,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BH,CAAA;AAExB,eAAO,MAAM,kBAAkB,eAAwC,CAAA;AAQvE;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,SAAS,CAKnE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,YAAY,CAoBzD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { toFunctionSelector } from 'viem';
|
|
2
|
+
import { namedParamOffsets } from '../../permissions.js';
|
|
3
|
+
import { cumulativeCap, pin, swapAction } from './rules.js';
|
|
4
|
+
/**
|
|
5
|
+
* fynd — Rhinestone's self-hosted Tycho aggregator.
|
|
6
|
+
*
|
|
7
|
+
* Unlike 0x there is no allowance-holder indirection: the TychoRouter is both
|
|
8
|
+
* the swap target and the ERC-20 approval spender, and every field we care about
|
|
9
|
+
* is a named static argument in the calldata head. That means nothing here is a
|
|
10
|
+
* magic offset — the rules are addressed by ABI parameter name.
|
|
11
|
+
*/
|
|
12
|
+
/** Chains with a deployed, whitelisted TychoRouter. */
|
|
13
|
+
export const FYND_CHAIN_IDS = [1, 56, 130, 137, 8453, 9745, 42161];
|
|
14
|
+
/**
|
|
15
|
+
* TychoRouter per chain — the contract fynd's encoded fills target.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors `fynd.routers` in the orchestrator's quoters.jsonnet. A chain is
|
|
18
|
+
* listed only when the router is whitelisted in `IntentExecutionPolicy`, since a
|
|
19
|
+
* quoter enabled without a whitelisted target produces quotes that revert.
|
|
20
|
+
*/
|
|
21
|
+
export const FYND_ROUTERS = {
|
|
22
|
+
1: '0xda892c989d07a18b5dd3f392d949f00df15c5736',
|
|
23
|
+
56: '0x99748cbd931cb367dad265c5b2b4bd306d448e99',
|
|
24
|
+
130: '0x764bc67b1036b00bc91221e988261f971a1c7ce4',
|
|
25
|
+
137: '0x7cb3e87095f6cf95982dc6f57445171a6d3b511c',
|
|
26
|
+
8453: '0x2d3524b9b5dae34b646614eebb1e038d403e4cac',
|
|
27
|
+
9745: '0x8f9b3b0451efff0ae8100428aee35fa3cbc0b769',
|
|
28
|
+
42161: '0xc1f838a5382bbb5729a0801c8ba73dfc861c4d34',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* TychoRouter `singleSwap`, the entrypoint fynd's encoded quotes call.
|
|
32
|
+
*
|
|
33
|
+
* Only the argument TYPES determine the selector and the head layout, and those
|
|
34
|
+
* are pinned by {@link FYND_SWAP_SELECTOR}'s assertion in the test suite. The
|
|
35
|
+
* NAMES of the first five are corroborated by observed fynd calldata; the
|
|
36
|
+
* trailing tuple's component names are our own labels and carry no guarantee —
|
|
37
|
+
* we never address them, since a tuple containing `bytes` is dynamic and sits
|
|
38
|
+
* behind a pointer.
|
|
39
|
+
*/
|
|
40
|
+
export const tychoRouterAbi = [
|
|
41
|
+
{
|
|
42
|
+
type: 'function',
|
|
43
|
+
name: 'singleSwap',
|
|
44
|
+
stateMutability: 'payable',
|
|
45
|
+
inputs: [
|
|
46
|
+
{ name: 'amountIn', type: 'uint256' },
|
|
47
|
+
{ name: 'tokenIn', type: 'address' },
|
|
48
|
+
{ name: 'tokenOut', type: 'address' },
|
|
49
|
+
{ name: 'minAmountOut', type: 'uint256' },
|
|
50
|
+
{ name: 'receiver', type: 'address' },
|
|
51
|
+
{
|
|
52
|
+
name: 'permit',
|
|
53
|
+
type: 'tuple',
|
|
54
|
+
components: [
|
|
55
|
+
{ name: 'nonce', type: 'uint16' },
|
|
56
|
+
{ name: 'token', type: 'address' },
|
|
57
|
+
{ name: 'amount', type: 'uint256' },
|
|
58
|
+
{ name: 'deadline', type: 'uint256' },
|
|
59
|
+
{ name: 'signature', type: 'bytes' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{ name: 'swap', type: 'bytes' },
|
|
63
|
+
],
|
|
64
|
+
outputs: [{ name: 'amountOut', type: 'uint256' }],
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
export const FYND_SWAP_SELECTOR = toFunctionSelector(tychoRouterAbi[0]);
|
|
68
|
+
/** Head offsets derived from the ABI — never hardcoded. */
|
|
69
|
+
const OFFSETS = namedParamOffsets(tychoRouterAbi, 'singleSwap');
|
|
70
|
+
/**
|
|
71
|
+
* Scope a session to fynd swaps.
|
|
72
|
+
*
|
|
73
|
+
* Needs no addresses: the router is a per-chain deployment the SDK knows, and
|
|
74
|
+
* because it is also the approval spender there is no separate allowance target
|
|
75
|
+
* to reason about.
|
|
76
|
+
*/
|
|
77
|
+
export function fynd(options = {}) {
|
|
78
|
+
return {
|
|
79
|
+
id: 'fynd',
|
|
80
|
+
...(options.maxSpend !== undefined ? { maxSpend: options.maxSpend } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function scopeFynd(ctx) {
|
|
84
|
+
const router = FYND_ROUTERS[ctx.chainId];
|
|
85
|
+
if (router === undefined) {
|
|
86
|
+
throw new Error(`fynd is not available on chain ${ctx.chainId}. ` +
|
|
87
|
+
`Supported: ${FYND_CHAIN_IDS.join(', ')}.`);
|
|
88
|
+
}
|
|
89
|
+
const rules = [
|
|
90
|
+
pin(OFFSETS.tokenIn, ctx.sellToken),
|
|
91
|
+
pin(OFFSETS.tokenOut, ctx.buyToken),
|
|
92
|
+
pin(OFFSETS.receiver, ctx.recipient),
|
|
93
|
+
];
|
|
94
|
+
if (ctx.cap !== undefined) {
|
|
95
|
+
rules.push(cumulativeCap(OFFSETS.amountIn, ctx.cap));
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
approveSpenders: [router],
|
|
99
|
+
actions: [swapAction(router, FYND_SWAP_SELECTOR, rules)],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
|
+
import type { RhinestoneSwapVenue, RoutedRhinestoneSwapVenue } from '../types.js';
|
|
3
|
+
import type { VenueContext, VenueScoping } from './rules.js';
|
|
4
|
+
/**
|
|
5
|
+
* The Rhinestone Swapper — the route the orchestrator actually uses for
|
|
6
|
+
* same-chain smart-account swaps, and the right layer to scope a session at.
|
|
7
|
+
*
|
|
8
|
+
* Scoping an aggregator directly (see `zero-ex.ts`, `fynd.ts`) describes a call
|
|
9
|
+
* the account does not make on this route: the orchestrator wraps whichever
|
|
10
|
+
* quoter wins behind its own Swapper, so a session scoped to 0x's
|
|
11
|
+
* AllowanceHolder rejects its own intended swap with `InvalidSignature()`.
|
|
12
|
+
* Verified live on Plasma — the account's sell token moves to the Swapper, and
|
|
13
|
+
* the aggregator never appears in the account's ops.
|
|
14
|
+
*
|
|
15
|
+
* Scoping here is also strictly better:
|
|
16
|
+
* - aggregator-agnostic, so whichever quoter wins is irrelevant
|
|
17
|
+
* - no 0x Settler rotation problem, because this contract is ours
|
|
18
|
+
* - one approve target per chain (the proxy) instead of one per venue
|
|
19
|
+
* - the fields we care about are typed head args on a contract we control,
|
|
20
|
+
* with the aggregator route demoted to an opaque `calls[]` tail
|
|
21
|
+
*
|
|
22
|
+
* What the Swapper guarantees (verified in compact-utils/src/swapper):
|
|
23
|
+
* at most `amountIn` of `tokenIn` is pulled, once, from `msg.sender`; the
|
|
24
|
+
* recipient's measured balance delta must be >= `minAmountOut` or it reverts;
|
|
25
|
+
* every refund and sweep goes to `msg.sender`. The account cannot be drained
|
|
26
|
+
* beyond the pulled amount: the tail runs as the Swapper, which holds no
|
|
27
|
+
* allowance from the account, and `SwapperLib.runRoute` forbids the tail from
|
|
28
|
+
* calling the proxy that does.
|
|
29
|
+
*
|
|
30
|
+
* Be precise about what that bounds, though. A capped `amountIn` bounds how
|
|
31
|
+
* much can LEAVE; it does not ensure anything comes back. `minAmountOut` is a
|
|
32
|
+
* caller-supplied argument this scoping does not pin, and the contract accepts
|
|
33
|
+
* zero — so with an unconstrained route a compromised key can spend up to the
|
|
34
|
+
* cap and receive nothing. Naming a venue closes that by pinning the
|
|
35
|
+
* route, leaving the pulled input nowhere to go but a real aggregator.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Both entrypoints share a head layout, which is what lets one venue cover
|
|
39
|
+
* both: `tokenIn`@0, sell amount@32, `tokenOut`@64, `recipient`@160. The sell
|
|
40
|
+
* amount is `amountIn` for exact-in and `amountInMax` for exact-out — in both
|
|
41
|
+
* cases the ceiling on what leaves the account, which is exactly what to cap.
|
|
42
|
+
*/
|
|
43
|
+
export declare const swapperAbi: readonly [{
|
|
44
|
+
readonly type: "function";
|
|
45
|
+
readonly name: "swapExactIn";
|
|
46
|
+
readonly stateMutability: "payable";
|
|
47
|
+
readonly inputs: readonly [{
|
|
48
|
+
readonly name: "tokenIn";
|
|
49
|
+
readonly type: "address";
|
|
50
|
+
}, {
|
|
51
|
+
readonly name: "amountIn";
|
|
52
|
+
readonly type: "uint256";
|
|
53
|
+
}, {
|
|
54
|
+
readonly name: "tokenOut";
|
|
55
|
+
readonly type: "address";
|
|
56
|
+
}, {
|
|
57
|
+
readonly name: "minAmountOut";
|
|
58
|
+
readonly type: "uint256";
|
|
59
|
+
}, {
|
|
60
|
+
readonly name: "quotedAmountOut";
|
|
61
|
+
readonly type: "uint256";
|
|
62
|
+
}, {
|
|
63
|
+
readonly name: "recipient";
|
|
64
|
+
readonly type: "address";
|
|
65
|
+
}, {
|
|
66
|
+
readonly name: "orderRef";
|
|
67
|
+
readonly type: "uint256";
|
|
68
|
+
}, {
|
|
69
|
+
readonly name: "calls";
|
|
70
|
+
readonly type: "tuple[]";
|
|
71
|
+
readonly components: readonly [{
|
|
72
|
+
readonly name: "target";
|
|
73
|
+
readonly type: "address";
|
|
74
|
+
}, {
|
|
75
|
+
readonly name: "value";
|
|
76
|
+
readonly type: "uint256";
|
|
77
|
+
}, {
|
|
78
|
+
readonly name: "data";
|
|
79
|
+
readonly type: "bytes";
|
|
80
|
+
}];
|
|
81
|
+
}];
|
|
82
|
+
readonly outputs: readonly [{
|
|
83
|
+
readonly name: "amountOut";
|
|
84
|
+
readonly type: "uint256";
|
|
85
|
+
}];
|
|
86
|
+
}, {
|
|
87
|
+
readonly type: "function";
|
|
88
|
+
readonly name: "swapExactOut";
|
|
89
|
+
readonly stateMutability: "payable";
|
|
90
|
+
readonly inputs: readonly [{
|
|
91
|
+
readonly name: "tokenIn";
|
|
92
|
+
readonly type: "address";
|
|
93
|
+
}, {
|
|
94
|
+
readonly name: "amountInMax";
|
|
95
|
+
readonly type: "uint256";
|
|
96
|
+
}, {
|
|
97
|
+
readonly name: "tokenOut";
|
|
98
|
+
readonly type: "address";
|
|
99
|
+
}, {
|
|
100
|
+
readonly name: "amountOut";
|
|
101
|
+
readonly type: "uint256";
|
|
102
|
+
}, {
|
|
103
|
+
readonly name: "quotedAmountIn";
|
|
104
|
+
readonly type: "uint256";
|
|
105
|
+
}, {
|
|
106
|
+
readonly name: "recipient";
|
|
107
|
+
readonly type: "address";
|
|
108
|
+
}, {
|
|
109
|
+
readonly name: "orderRef";
|
|
110
|
+
readonly type: "uint256";
|
|
111
|
+
}, {
|
|
112
|
+
readonly name: "calls";
|
|
113
|
+
readonly type: "tuple[]";
|
|
114
|
+
readonly components: readonly [{
|
|
115
|
+
readonly name: "target";
|
|
116
|
+
readonly type: "address";
|
|
117
|
+
}, {
|
|
118
|
+
readonly name: "value";
|
|
119
|
+
readonly type: "uint256";
|
|
120
|
+
}, {
|
|
121
|
+
readonly name: "data";
|
|
122
|
+
readonly type: "bytes";
|
|
123
|
+
}];
|
|
124
|
+
}];
|
|
125
|
+
readonly outputs: readonly [{
|
|
126
|
+
readonly name: "amountSpent";
|
|
127
|
+
readonly type: "uint256";
|
|
128
|
+
}];
|
|
129
|
+
}];
|
|
130
|
+
export declare const SWAP_EXACT_IN_SELECTOR: `0x${string}`;
|
|
131
|
+
export declare const SWAP_EXACT_OUT_SELECTOR: `0x${string}`;
|
|
132
|
+
export declare function swapperAddresses(environment: 'production' | 'development'): {
|
|
133
|
+
swapper: Address;
|
|
134
|
+
proxy: Address;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Route swaps through the Rhinestone Swapper — the default, and the only venue
|
|
138
|
+
* that matches what the orchestrator emits for same-chain smart-account swaps.
|
|
139
|
+
*
|
|
140
|
+
* Takes no addresses and no aggregator: which quoter fills the swap is the
|
|
141
|
+
* orchestrator's decision, and this scoping holds whichever one wins.
|
|
142
|
+
*/
|
|
143
|
+
export declare function rhinestoneSwap(options?: {
|
|
144
|
+
maxSpend?: bigint;
|
|
145
|
+
}): RhinestoneSwapVenue;
|
|
146
|
+
export declare function scopeRhinestone(venue: RoutedRhinestoneSwapVenue, ctx: VenueContext): VenueScoping;
|
|
147
|
+
//# sourceMappingURL=rhinestone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rhinestone.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/smart-sessions/swap/rhinestone.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAgC,MAAM,MAAM,CAAA;AAE3E,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAE1B,MAAM,UAAU,CAAA;AAEjB,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAIzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDC,CAAA;AAExB,eAAO,MAAM,sBAAsB,eAAoC,CAAA;AACvE,eAAO,MAAM,uBAAuB,eAAoC,CAAA;AAqBxE,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,YAAY,GAAG,aAAa,GAAG;IAC3E,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAIA;AAkJD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,mBAAmB,CAKrB;AACD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,yBAAyB,EAChC,GAAG,EAAE,YAAY,GAChB,YAAY,CAgFd"}
|