@0xsequence/wallet-core 3.0.0 → 3.0.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +20 -0
- package/dist/signers/session/explicit.d.ts.map +1 -1
- package/dist/signers/session/explicit.js +3 -6
- package/dist/signers/session/session.d.ts +1 -0
- package/dist/signers/session/session.d.ts.map +1 -1
- package/dist/signers/session/session.js +7 -0
- package/dist/signers/session-manager.d.ts +4 -0
- package/dist/signers/session-manager.d.ts.map +1 -1
- package/dist/signers/session-manager.js +65 -22
- package/dist/wallet.js +2 -2
- package/package.json +4 -4
- package/src/signers/session/explicit.ts +3 -11
- package/src/signers/session/session.ts +10 -2
- package/src/signers/session-manager.ts +82 -25
- package/src/wallet.ts +2 -2
- package/test/constants.ts +2 -0
- package/test/session-manager.test.ts +345 -3
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @0xsequence/wallet-core
|
|
2
2
|
|
|
3
|
+
## 3.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- allow native self transfer
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @0xsequence/guard@3.0.2
|
|
10
|
+
- @0xsequence/relayer@3.0.2
|
|
11
|
+
- @0xsequence/wallet-primitives@3.0.2
|
|
12
|
+
|
|
13
|
+
## 3.0.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Network and session fixes
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @0xsequence/guard@3.0.1
|
|
20
|
+
- @0xsequence/relayer@3.0.1
|
|
21
|
+
- @0xsequence/wallet-primitives@3.0.1
|
|
22
|
+
|
|
3
23
|
## 3.0.0
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explicit.d.ts","sourceRoot":"","sources":["../../../src/signers/session/explicit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAC/G,OAAO,EAA8B,OAAO,EAAe,GAAG,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AACpF,OAAO,EAAiB,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"explicit.d.ts","sourceRoot":"","sources":["../../../src/signers/session/explicit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAC/G,OAAO,EAA8B,OAAO,EAAe,GAAG,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AACpF,OAAO,EAAiB,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,qBAAqB,EAAmB,qBAAqB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAExG,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;AAI1E,qBAAa,QAAS,YAAW,qBAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,SAAgB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAA;IACxC,SAAgB,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,CAAA;gBAErD,UAAU,EAAE,GAAG,CAAC,GAAG,GAAG,OAAO,EAAE,kBAAkB,EAAE,cAAc;IAS7E,OAAO,CAAC,eAAe,EAAE,aAAa,CAAC,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,qBAAqB;IA+C1F,uBAAuB,CAC3B,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,CAAC,IAAI,EAClB,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAC3B,OAAO,CAAC,UAAU,CAAC,UAAU,GAAG,SAAS,CAAC;IAmC7C,OAAO,CAAC,sBAAsB;IAmB9B,OAAO,CAAC,iBAAiB;IAYnB,kBAAkB,CACtB,UAAU,EAAE,UAAU,CAAC,UAAU,EACjC,IAAI,EAAE,OAAO,CAAC,IAAI,EAClB,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAC3B,OAAO,CAAC,OAAO,CAAC;IAsDb,aAAa,CACjB,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,CAAC,IAAI,EAClB,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAC3B,OAAO,CAAC,OAAO,CAAC;IAab,QAAQ,CACZ,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,KAAK,EACtB,OAAO,EAAE,MAAM,EACf,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAC3B,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC;YA6BnC,qBAAqB;IAwB7B,iBAAiB,CACrB,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,EACrB,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,GAC1B,OAAO,CAAC,UAAU,EAAE,CAAC;CA8EzB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Constants, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives';
|
|
2
2
|
import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex } from 'ox';
|
|
3
3
|
import { MemoryPkStore } from '../pk/index.js';
|
|
4
|
+
import { isIncrementCall } from './session.js';
|
|
4
5
|
const VALUE_TRACKING_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
|
5
6
|
export class Explicit {
|
|
6
7
|
_privateKey;
|
|
@@ -143,9 +144,7 @@ export class Explicit {
|
|
|
143
144
|
return true;
|
|
144
145
|
}
|
|
145
146
|
async supportedCall(wallet, chainId, call, sessionManagerAddress, provider) {
|
|
146
|
-
if (
|
|
147
|
-
Hex.size(call.data) > 4 &&
|
|
148
|
-
Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT))) {
|
|
147
|
+
if (isIncrementCall(call, sessionManagerAddress)) {
|
|
149
148
|
// Can sign increment usage calls
|
|
150
149
|
return true;
|
|
151
150
|
}
|
|
@@ -158,9 +157,7 @@ export class Explicit {
|
|
|
158
157
|
async signCall(wallet, chainId, payload, callIdx, sessionManagerAddress, provider) {
|
|
159
158
|
const call = payload.calls[callIdx];
|
|
160
159
|
let permissionIndex;
|
|
161
|
-
if (
|
|
162
|
-
Hex.size(call.data) > 4 &&
|
|
163
|
-
Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT))) {
|
|
160
|
+
if (isIncrementCall(call, sessionManagerAddress)) {
|
|
164
161
|
// Permission check not required. Use the first permission
|
|
165
162
|
permissionIndex = 0;
|
|
166
163
|
}
|
|
@@ -23,4 +23,5 @@ export interface ImplicitSessionSigner extends SessionSigner {
|
|
|
23
23
|
}
|
|
24
24
|
export declare function isExplicitSessionSigner(signer: SessionSigner): signer is ExplicitSessionSigner;
|
|
25
25
|
export declare function isImplicitSessionSigner(signer: SessionSigner): signer is ImplicitSessionSigner;
|
|
26
|
+
export declare function isIncrementCall(call: Payload.Call, sessionManagerAddress: Address.Address): boolean;
|
|
26
27
|
//# sourceMappingURL=session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/signers/session/session.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/signers/session/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AACnG,OAAO,EAAe,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AAExD,MAAM,MAAM,0BAA0B,GAClC,SAAS,GACT,mBAAmB,GACnB,sBAAsB,GACtB,qBAAqB,GACrB,0BAA0B,GAC1B,2BAA2B,GAC3B,0BAA0B,GAC1B,aAAa,CAAA;AAEjB,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,0BAA0B,CAAA;CAC3C,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAGnD,OAAO,EAAE,CAAC,eAAe,EAAE,aAAa,CAAC,gBAAgB,EAAE,OAAO,EAAE,MAAM,KAAK,qBAAqB,CAAA;IAGpG,aAAa,EAAE,CACb,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,CAAC,IAAI,EAClB,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,KACzB,OAAO,CAAC,OAAO,CAAC,CAAA;IAGrB,QAAQ,EAAE,CACR,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,KAAK,EACtB,OAAO,EAAE,MAAM,EACf,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,KACzB,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;CACpD;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,iBAAiB,EAAE,CACjB,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,EACrB,qBAAqB,EAAE,OAAO,CAAC,OAAO,EACtC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,KACxB,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,cAAc,EAAE,OAAO,CAAC,OAAO,CAAA;CAChC;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,IAAI,qBAAqB,CAE9F;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,IAAI,qBAAqB,CAE9F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,qBAAqB,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAMnG"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { Constants } from '@0xsequence/wallet-primitives';
|
|
2
|
+
import { AbiFunction, Address, Hex } from 'ox';
|
|
1
3
|
export function isExplicitSessionSigner(signer) {
|
|
2
4
|
return 'prepareIncrements' in signer;
|
|
3
5
|
}
|
|
4
6
|
export function isImplicitSessionSigner(signer) {
|
|
5
7
|
return 'identitySigner' in signer;
|
|
6
8
|
}
|
|
9
|
+
export function isIncrementCall(call, sessionManagerAddress) {
|
|
10
|
+
return (Address.isEqual(call.to, sessionManagerAddress) &&
|
|
11
|
+
Hex.size(call.data) >= 4 &&
|
|
12
|
+
Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)));
|
|
13
|
+
}
|
|
@@ -31,6 +31,10 @@ export declare class SessionManager implements SapientSigner {
|
|
|
31
31
|
isValid: boolean;
|
|
32
32
|
invalidReason?: SessionSignerInvalidReason;
|
|
33
33
|
}[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Find one signer per call from the given candidate list (first that supports each call).
|
|
36
|
+
*/
|
|
37
|
+
private findSignersForCallsWithCandidates;
|
|
34
38
|
findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise<SessionSigner[]>;
|
|
35
39
|
prepareIncrement(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise<Payload.Call | null>;
|
|
36
40
|
signSapient(wallet: Address.Address, chainId: number, payload: Payload.Parented, imageHash: Hex.Hex): Promise<SignatureTypes.SignatureOfSapientSignerLeaf>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/signers/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,OAAO,EACP,aAAa,EAEb,SAAS,IAAI,cAAc,EAC5B,MAAM,+BAA+B,CAAA;AACtC,OAAO,EAAe,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AACxD,OAAO,KAAK,KAAK,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EACL,QAAQ,EACR,QAAQ,EAER,aAAa,EACb,0BAA0B,
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/signers/session-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Config, Constants, Extensions, Payload, SessionConfig, SessionSignature, } from '@0xsequence/wallet-primitives';
|
|
2
2
|
import { AbiFunction, Address, Hex } from 'ox';
|
|
3
|
-
import { isExplicitSessionSigner, isImplicitSessionSigner, } from './session/index.js';
|
|
3
|
+
import { isExplicitSessionSigner, isImplicitSessionSigner, isIncrementCall, } from './session/index.js';
|
|
4
4
|
const MAX_SPACE = 2n ** 80n - 1n;
|
|
5
5
|
export class SessionManager {
|
|
6
6
|
wallet;
|
|
@@ -86,19 +86,10 @@ export class SessionManager {
|
|
|
86
86
|
invalidReason,
|
|
87
87
|
}));
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (identitySigners.length === 0) {
|
|
94
|
-
throw new Error('Identity signers not found');
|
|
95
|
-
}
|
|
96
|
-
// Prioritize implicit signers
|
|
97
|
-
const availableSigners = [...this._implicitSigners, ...this._explicitSigners];
|
|
98
|
-
if (availableSigners.length === 0) {
|
|
99
|
-
throw new Error('No signers match the topology');
|
|
100
|
-
}
|
|
101
|
-
// Find supported signers for each call
|
|
89
|
+
/**
|
|
90
|
+
* Find one signer per call from the given candidate list (first that supports each call).
|
|
91
|
+
*/
|
|
92
|
+
async findSignersForCallsWithCandidates(wallet, chainId, calls, topology, availableSigners) {
|
|
102
93
|
const signers = [];
|
|
103
94
|
for (const call of calls) {
|
|
104
95
|
let supported = false;
|
|
@@ -128,7 +119,56 @@ export class SessionManager {
|
|
|
128
119
|
if (expiredSupportedSigner) {
|
|
129
120
|
throw new Error(`Signer supporting call is expired: ${expiredSupportedSigner.address}`);
|
|
130
121
|
}
|
|
131
|
-
throw new Error(`No signer supported for call.
|
|
122
|
+
throw new Error(`No signer supported for call. Call: to=${call.to}, data=${call.data}, value=${call.value}, `);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return signers;
|
|
126
|
+
}
|
|
127
|
+
async findSignersForCalls(wallet, chainId, calls) {
|
|
128
|
+
const topology = await this.topology;
|
|
129
|
+
const identitySigners = SessionConfig.getIdentitySigners(topology);
|
|
130
|
+
if (identitySigners.length === 0) {
|
|
131
|
+
throw new Error('Identity signers not found');
|
|
132
|
+
}
|
|
133
|
+
const availableSigners = [...this._implicitSigners, ...this._explicitSigners];
|
|
134
|
+
if (availableSigners.length === 0) {
|
|
135
|
+
throw new Error('No signers match the topology');
|
|
136
|
+
}
|
|
137
|
+
const nonIncrementCalls = [];
|
|
138
|
+
const incrementCalls = [];
|
|
139
|
+
for (const call of calls) {
|
|
140
|
+
if (isIncrementCall(call, this.address)) {
|
|
141
|
+
incrementCalls.push(call);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
nonIncrementCalls.push(call);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Find signers for non-increment calls
|
|
148
|
+
const nonIncrementSigners = nonIncrementCalls.length > 0
|
|
149
|
+
? await this.findSignersForCallsWithCandidates(wallet, chainId, nonIncrementCalls, topology, availableSigners)
|
|
150
|
+
: [];
|
|
151
|
+
let incrementSigners = [];
|
|
152
|
+
if (incrementCalls.length > 0) {
|
|
153
|
+
// Find signers for increment calls, preferring signers that signed non-increment calls
|
|
154
|
+
const incrementCandidates = [
|
|
155
|
+
...nonIncrementSigners,
|
|
156
|
+
...availableSigners.filter((s) => !nonIncrementSigners.includes(s)),
|
|
157
|
+
];
|
|
158
|
+
incrementSigners = await this.findSignersForCallsWithCandidates(wallet, chainId, incrementCalls, topology, incrementCandidates);
|
|
159
|
+
}
|
|
160
|
+
// Merge back in original call order
|
|
161
|
+
const signers = [];
|
|
162
|
+
let nonIncrementIndex = 0;
|
|
163
|
+
let incrementIndex = 0;
|
|
164
|
+
for (const call of calls) {
|
|
165
|
+
if (isIncrementCall(call, this.address)) {
|
|
166
|
+
signers.push(incrementSigners[incrementIndex]);
|
|
167
|
+
incrementIndex++;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
signers.push(nonIncrementSigners[nonIncrementIndex]);
|
|
171
|
+
nonIncrementIndex++;
|
|
132
172
|
}
|
|
133
173
|
}
|
|
134
174
|
return signers;
|
|
@@ -138,17 +178,20 @@ export class SessionManager {
|
|
|
138
178
|
throw new Error('No calls provided');
|
|
139
179
|
}
|
|
140
180
|
const signers = await this.findSignersForCalls(wallet, chainId, calls);
|
|
141
|
-
//
|
|
142
|
-
const
|
|
181
|
+
// Map each signer to only their non-increment calls
|
|
182
|
+
const signerToNonIncrementCalls = new Map();
|
|
143
183
|
signers.forEach((signer, index) => {
|
|
144
184
|
const call = calls[index];
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
if (isIncrementCall(call, this.address)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const existing = signerToNonIncrementCalls.get(signer) || [];
|
|
189
|
+
signerToNonIncrementCalls.set(signer, [...existing, call]);
|
|
147
190
|
});
|
|
148
|
-
// Prepare increments for each explicit signer
|
|
149
|
-
const increments = (await Promise.all(Array.from(
|
|
191
|
+
// Prepare increments for each explicit signer from their non-increment calls only
|
|
192
|
+
const increments = (await Promise.all(Array.from(signerToNonIncrementCalls.entries()).map(async ([signer, nonIncrementCalls]) => {
|
|
150
193
|
if (isExplicitSessionSigner(signer)) {
|
|
151
|
-
return signer.prepareIncrements(wallet, chainId,
|
|
194
|
+
return signer.prepareIncrements(wallet, chainId, nonIncrementCalls, this.address, this._provider);
|
|
152
195
|
}
|
|
153
196
|
return [];
|
|
154
197
|
}))).flat();
|
package/dist/wallet.js
CHANGED
|
@@ -243,7 +243,7 @@ export class Wallet {
|
|
|
243
243
|
if (call.delegateCall) {
|
|
244
244
|
throw new Error('delegate calls are not allowed in safe mode');
|
|
245
245
|
}
|
|
246
|
-
if (Address.isEqual(call.to, this.address)) {
|
|
246
|
+
if (Address.isEqual(call.to, this.address) && call.data !== '0x') {
|
|
247
247
|
throw new Error('calls to the wallet contract itself are not allowed in safe mode');
|
|
248
248
|
}
|
|
249
249
|
}
|
|
@@ -326,7 +326,7 @@ export class Wallet {
|
|
|
326
326
|
if (call.delegateCall) {
|
|
327
327
|
throw new Error('delegate calls are not allowed in safe mode');
|
|
328
328
|
}
|
|
329
|
-
if (Address.isEqual(call.to, this.address)) {
|
|
329
|
+
if (Address.isEqual(call.to, this.address) && call.data !== '0x') {
|
|
330
330
|
throw new Error('calls to the wallet contract itself are not allowed in safe mode');
|
|
331
331
|
}
|
|
332
332
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xsequence/wallet-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"mipd": "^0.0.7",
|
|
28
28
|
"ox": "^0.9.17",
|
|
29
29
|
"viem": "^2.40.3",
|
|
30
|
-
"@0xsequence/
|
|
31
|
-
"@0xsequence/
|
|
32
|
-
"@0xsequence/
|
|
30
|
+
"@0xsequence/relayer": "^3.0.2",
|
|
31
|
+
"@0xsequence/guard": "^3.0.2",
|
|
32
|
+
"@0xsequence/wallet-primitives": "^3.0.2"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Constants, Payload, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives'
|
|
2
2
|
import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider } from 'ox'
|
|
3
3
|
import { MemoryPkStore, PkStore } from '../pk/index.js'
|
|
4
|
-
import { ExplicitSessionSigner, SessionSignerValidity, UsageLimit } from './session.js'
|
|
4
|
+
import { ExplicitSessionSigner, isIncrementCall, SessionSignerValidity, UsageLimit } from './session.js'
|
|
5
5
|
|
|
6
6
|
export type ExplicitParams = Omit<Permission.SessionPermissions, 'signer'>
|
|
7
7
|
|
|
@@ -208,11 +208,7 @@ export class Explicit implements ExplicitSessionSigner {
|
|
|
208
208
|
sessionManagerAddress: Address.Address,
|
|
209
209
|
provider?: Provider.Provider,
|
|
210
210
|
): Promise<boolean> {
|
|
211
|
-
if (
|
|
212
|
-
Address.isEqual(call.to, sessionManagerAddress) &&
|
|
213
|
-
Hex.size(call.data) > 4 &&
|
|
214
|
-
Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT))
|
|
215
|
-
) {
|
|
211
|
+
if (isIncrementCall(call, sessionManagerAddress)) {
|
|
216
212
|
// Can sign increment usage calls
|
|
217
213
|
return true
|
|
218
214
|
}
|
|
@@ -234,11 +230,7 @@ export class Explicit implements ExplicitSessionSigner {
|
|
|
234
230
|
): Promise<SessionSignature.SessionCallSignature> {
|
|
235
231
|
const call = payload.calls[callIdx]!
|
|
236
232
|
let permissionIndex: number
|
|
237
|
-
if (
|
|
238
|
-
Address.isEqual(call.to, sessionManagerAddress) &&
|
|
239
|
-
Hex.size(call.data) > 4 &&
|
|
240
|
-
Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT))
|
|
241
|
-
) {
|
|
233
|
+
if (isIncrementCall(call, sessionManagerAddress)) {
|
|
242
234
|
// Permission check not required. Use the first permission
|
|
243
235
|
permissionIndex = 0
|
|
244
236
|
} else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives'
|
|
2
|
-
import { Address, Hex, Provider } from 'ox'
|
|
1
|
+
import { Constants, Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives'
|
|
2
|
+
import { AbiFunction, Address, Hex, Provider } from 'ox'
|
|
3
3
|
|
|
4
4
|
export type SessionSignerInvalidReason =
|
|
5
5
|
| 'Expired'
|
|
@@ -68,3 +68,11 @@ export function isExplicitSessionSigner(signer: SessionSigner): signer is Explic
|
|
|
68
68
|
export function isImplicitSessionSigner(signer: SessionSigner): signer is ImplicitSessionSigner {
|
|
69
69
|
return 'identitySigner' in signer
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
export function isIncrementCall(call: Payload.Call, sessionManagerAddress: Address.Address): boolean {
|
|
73
|
+
return (
|
|
74
|
+
Address.isEqual(call.to, sessionManagerAddress) &&
|
|
75
|
+
Hex.size(call.data) >= 4 &&
|
|
76
|
+
Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT))
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
SessionSigner,
|
|
19
19
|
SessionSignerInvalidReason,
|
|
20
20
|
isImplicitSessionSigner,
|
|
21
|
+
isIncrementCall,
|
|
21
22
|
UsageLimit,
|
|
22
23
|
} from './session/index.js'
|
|
23
24
|
|
|
@@ -130,21 +131,16 @@ export class SessionManager implements SapientSigner {
|
|
|
130
131
|
}))
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (availableSigners.length === 0) {
|
|
144
|
-
throw new Error('No signers match the topology')
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Find supported signers for each call
|
|
134
|
+
/**
|
|
135
|
+
* Find one signer per call from the given candidate list (first that supports each call).
|
|
136
|
+
*/
|
|
137
|
+
private async findSignersForCallsWithCandidates(
|
|
138
|
+
wallet: Address.Address,
|
|
139
|
+
chainId: number,
|
|
140
|
+
calls: Payload.Call[],
|
|
141
|
+
topology: SessionConfig.SessionsTopology,
|
|
142
|
+
availableSigners: SessionSigner[],
|
|
143
|
+
): Promise<SessionSigner[]> {
|
|
148
144
|
const signers: SessionSigner[] = []
|
|
149
145
|
for (const call of calls) {
|
|
150
146
|
let supported = false
|
|
@@ -173,9 +169,67 @@ export class SessionManager implements SapientSigner {
|
|
|
173
169
|
if (expiredSupportedSigner) {
|
|
174
170
|
throw new Error(`Signer supporting call is expired: ${expiredSupportedSigner.address}`)
|
|
175
171
|
}
|
|
176
|
-
throw new Error(
|
|
177
|
-
|
|
178
|
-
|
|
172
|
+
throw new Error(`No signer supported for call. Call: to=${call.to}, data=${call.data}, value=${call.value}, `)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return signers
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise<SessionSigner[]> {
|
|
179
|
+
const topology = await this.topology
|
|
180
|
+
const identitySigners = SessionConfig.getIdentitySigners(topology)
|
|
181
|
+
if (identitySigners.length === 0) {
|
|
182
|
+
throw new Error('Identity signers not found')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const availableSigners = [...this._implicitSigners, ...this._explicitSigners]
|
|
186
|
+
if (availableSigners.length === 0) {
|
|
187
|
+
throw new Error('No signers match the topology')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const nonIncrementCalls: Payload.Call[] = []
|
|
191
|
+
const incrementCalls: Payload.Call[] = []
|
|
192
|
+
for (const call of calls) {
|
|
193
|
+
if (isIncrementCall(call, this.address)) {
|
|
194
|
+
incrementCalls.push(call)
|
|
195
|
+
} else {
|
|
196
|
+
nonIncrementCalls.push(call)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Find signers for non-increment calls
|
|
201
|
+
const nonIncrementSigners =
|
|
202
|
+
nonIncrementCalls.length > 0
|
|
203
|
+
? await this.findSignersForCallsWithCandidates(wallet, chainId, nonIncrementCalls, topology, availableSigners)
|
|
204
|
+
: []
|
|
205
|
+
|
|
206
|
+
let incrementSigners: SessionSigner[] = []
|
|
207
|
+
if (incrementCalls.length > 0) {
|
|
208
|
+
// Find signers for increment calls, preferring signers that signed non-increment calls
|
|
209
|
+
const incrementCandidates = [
|
|
210
|
+
...nonIncrementSigners,
|
|
211
|
+
...availableSigners.filter((s) => !nonIncrementSigners.includes(s)),
|
|
212
|
+
]
|
|
213
|
+
incrementSigners = await this.findSignersForCallsWithCandidates(
|
|
214
|
+
wallet,
|
|
215
|
+
chainId,
|
|
216
|
+
incrementCalls,
|
|
217
|
+
topology,
|
|
218
|
+
incrementCandidates,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Merge back in original call order
|
|
223
|
+
const signers: SessionSigner[] = []
|
|
224
|
+
let nonIncrementIndex = 0
|
|
225
|
+
let incrementIndex = 0
|
|
226
|
+
for (const call of calls) {
|
|
227
|
+
if (isIncrementCall(call, this.address)) {
|
|
228
|
+
signers.push(incrementSigners[incrementIndex]!)
|
|
229
|
+
incrementIndex++
|
|
230
|
+
} else {
|
|
231
|
+
signers.push(nonIncrementSigners[nonIncrementIndex]!)
|
|
232
|
+
nonIncrementIndex++
|
|
179
233
|
}
|
|
180
234
|
}
|
|
181
235
|
return signers
|
|
@@ -191,20 +245,23 @@ export class SessionManager implements SapientSigner {
|
|
|
191
245
|
}
|
|
192
246
|
const signers = await this.findSignersForCalls(wallet, chainId, calls)
|
|
193
247
|
|
|
194
|
-
//
|
|
195
|
-
const
|
|
248
|
+
// Map each signer to only their non-increment calls
|
|
249
|
+
const signerToNonIncrementCalls = new Map<SessionSigner, Payload.Call[]>()
|
|
196
250
|
signers.forEach((signer, index) => {
|
|
197
251
|
const call = calls[index]!
|
|
198
|
-
|
|
199
|
-
|
|
252
|
+
if (isIncrementCall(call, this.address)) {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
const existing = signerToNonIncrementCalls.get(signer) || []
|
|
256
|
+
signerToNonIncrementCalls.set(signer, [...existing, call])
|
|
200
257
|
})
|
|
201
258
|
|
|
202
|
-
// Prepare increments for each explicit signer
|
|
259
|
+
// Prepare increments for each explicit signer from their non-increment calls only
|
|
203
260
|
const increments: UsageLimit[] = (
|
|
204
261
|
await Promise.all(
|
|
205
|
-
Array.from(
|
|
262
|
+
Array.from(signerToNonIncrementCalls.entries()).map(async ([signer, nonIncrementCalls]) => {
|
|
206
263
|
if (isExplicitSessionSigner(signer)) {
|
|
207
|
-
return signer.prepareIncrements(wallet, chainId,
|
|
264
|
+
return signer.prepareIncrements(wallet, chainId, nonIncrementCalls, this.address, this._provider!)
|
|
208
265
|
}
|
|
209
266
|
return []
|
|
210
267
|
}),
|
package/src/wallet.ts
CHANGED
|
@@ -342,7 +342,7 @@ export class Wallet {
|
|
|
342
342
|
if (call.delegateCall) {
|
|
343
343
|
throw new Error('delegate calls are not allowed in safe mode')
|
|
344
344
|
}
|
|
345
|
-
if (Address.isEqual(call.to, this.address)) {
|
|
345
|
+
if (Address.isEqual(call.to, this.address) && call.data !== '0x') {
|
|
346
346
|
throw new Error('calls to the wallet contract itself are not allowed in safe mode')
|
|
347
347
|
}
|
|
348
348
|
}
|
|
@@ -455,7 +455,7 @@ export class Wallet {
|
|
|
455
455
|
if (call.delegateCall) {
|
|
456
456
|
throw new Error('delegate calls are not allowed in safe mode')
|
|
457
457
|
}
|
|
458
|
-
if (Address.isEqual(call.to, this.address)) {
|
|
458
|
+
if (Address.isEqual(call.to, this.address) && call.data !== '0x') {
|
|
459
459
|
throw new Error('calls to the wallet contract itself are not allowed in safe mode')
|
|
460
460
|
}
|
|
461
461
|
}
|
package/test/constants.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { Abi, AbiEvent, Address } from 'ox'
|
|
|
5
5
|
const envFile = process.env.CI ? '.env.test' : '.env.test.local'
|
|
6
6
|
dotenvConfig({ path: envFile })
|
|
7
7
|
|
|
8
|
+
// Contracts are deployed on Arbitrum
|
|
9
|
+
|
|
8
10
|
// Requires https://example.com redirectUrl
|
|
9
11
|
export const EMITTER_ADDRESS1: Address.Address = '0xad90eB52BC180Bd9f66f50981E196f3E996278D3'
|
|
10
12
|
// Requires https://another-example.com redirectUrl
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Extensions } from '@0xsequence/wallet-primitives'
|
|
1
|
+
import { Constants, Extensions } from '@0xsequence/wallet-primitives'
|
|
2
2
|
import { AbiEvent, AbiFunction, Address, Bytes, Hex, Provider, RpcTransport, Secp256k1 } from 'ox'
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
import { Attestation, GenericTree, Payload, Permission, SessionConfig } from '../../primitives/src/index.js'
|
|
@@ -731,7 +731,7 @@ for (const extension of ALL_EXTENSIONS) {
|
|
|
731
731
|
)
|
|
732
732
|
|
|
733
733
|
it(
|
|
734
|
-
'signs a payload using an explicit session',
|
|
734
|
+
'signs a payload using an explicit session with allowAll and value limit',
|
|
735
735
|
async () => {
|
|
736
736
|
const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
|
|
737
737
|
const chainId = Number(await provider.request({ method: 'eth_chainId' }))
|
|
@@ -804,7 +804,7 @@ for (const extension of ALL_EXTENSIONS) {
|
|
|
804
804
|
)
|
|
805
805
|
|
|
806
806
|
it(
|
|
807
|
-
'signs
|
|
807
|
+
'signs using explicit session with onlyOnce, consumes usage and rejects second call',
|
|
808
808
|
async () => {
|
|
809
809
|
const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
|
|
810
810
|
const chainId = Number(await provider.request({ method: 'eth_chainId' }))
|
|
@@ -1357,5 +1357,347 @@ for (const extension of ALL_EXTENSIONS) {
|
|
|
1357
1357
|
},
|
|
1358
1358
|
timeout,
|
|
1359
1359
|
)
|
|
1360
|
+
|
|
1361
|
+
it(
|
|
1362
|
+
'two explicit sessions with same value limit: exhaust first then second send uses second session (increment from non-increment calls only)',
|
|
1363
|
+
async () => {
|
|
1364
|
+
const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
|
|
1365
|
+
const chainId = Number(await provider.request({ method: 'eth_chainId' }))
|
|
1366
|
+
|
|
1367
|
+
const identityPrivateKey = Secp256k1.randomPrivateKey()
|
|
1368
|
+
const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey }))
|
|
1369
|
+
const stateProvider = new State.Local.Provider()
|
|
1370
|
+
|
|
1371
|
+
const targetAddress = randomAddress()
|
|
1372
|
+
const valueLimit = 500000000000000000n // 0.5 ETH per session
|
|
1373
|
+
|
|
1374
|
+
const sessionPermission: ExplicitSessionConfig = {
|
|
1375
|
+
chainId,
|
|
1376
|
+
valueLimit,
|
|
1377
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
1378
|
+
permissions: [PermissionBuilder.for(targetAddress).allowAll().build()],
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const explicitPrivateKey1 = Secp256k1.randomPrivateKey()
|
|
1382
|
+
const explicitSigner1 = new Signers.Session.Explicit(explicitPrivateKey1, {
|
|
1383
|
+
...sessionPermission,
|
|
1384
|
+
})
|
|
1385
|
+
|
|
1386
|
+
const explicitPrivateKey2 = Secp256k1.randomPrivateKey()
|
|
1387
|
+
const explicitSigner2 = new Signers.Session.Explicit(explicitPrivateKey2, {
|
|
1388
|
+
...sessionPermission,
|
|
1389
|
+
})
|
|
1390
|
+
|
|
1391
|
+
let sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), {
|
|
1392
|
+
...sessionPermission,
|
|
1393
|
+
signer: explicitSigner1.address,
|
|
1394
|
+
})
|
|
1395
|
+
sessionTopology = SessionConfig.addExplicitSession(sessionTopology, {
|
|
1396
|
+
...sessionPermission,
|
|
1397
|
+
signer: explicitSigner2.address,
|
|
1398
|
+
})
|
|
1399
|
+
await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1400
|
+
const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1401
|
+
|
|
1402
|
+
const wallet = await Wallet.fromConfiguration(
|
|
1403
|
+
{
|
|
1404
|
+
threshold: 1n,
|
|
1405
|
+
checkpoint: 0n,
|
|
1406
|
+
topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)],
|
|
1407
|
+
},
|
|
1408
|
+
{ stateProvider },
|
|
1409
|
+
)
|
|
1410
|
+
// Fund wallet with 2 ETH so we can send 0.5 ETH twice (each tx needs value + gas)
|
|
1411
|
+
await provider.request({
|
|
1412
|
+
method: 'anvil_setBalance',
|
|
1413
|
+
params: [wallet.address, Hex.fromNumber(2n * 1000000000000000000n)],
|
|
1414
|
+
})
|
|
1415
|
+
|
|
1416
|
+
const sessionManager = new Signers.SessionManager(wallet, {
|
|
1417
|
+
provider,
|
|
1418
|
+
sessionManagerAddress: extension.sessions,
|
|
1419
|
+
explicitSigners: [explicitSigner1, explicitSigner2],
|
|
1420
|
+
})
|
|
1421
|
+
|
|
1422
|
+
const call: Payload.Call = {
|
|
1423
|
+
to: targetAddress,
|
|
1424
|
+
value: valueLimit, // one full limit
|
|
1425
|
+
data: '0x' as Hex.Hex,
|
|
1426
|
+
gasLimit: 0n,
|
|
1427
|
+
delegateCall: false,
|
|
1428
|
+
onlyFallback: false,
|
|
1429
|
+
behaviorOnError: 'revert',
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// First send: uses first session (exhausts it)
|
|
1433
|
+
const increment1 = await sessionManager.prepareIncrement(wallet.address, chainId, [call])
|
|
1434
|
+
expect(increment1).not.toBeNull()
|
|
1435
|
+
const calls1 = includeIncrement([call], increment1!, extension.sessions)
|
|
1436
|
+
const tx1 = await buildAndSignCall(wallet, sessionManager, calls1, provider, chainId)
|
|
1437
|
+
await simulateTransaction(provider, tx1)
|
|
1438
|
+
|
|
1439
|
+
// Second send: same call. First session is exhausted so findSignersForCalls picks second session.
|
|
1440
|
+
// Payload is [call, increment] (or [increment, call]). signSapient must derive expectedIncrement
|
|
1441
|
+
// from non-increment calls only so it matches the increment we built for the second session.
|
|
1442
|
+
const increment2 = await sessionManager.prepareIncrement(wallet.address, chainId, [call])
|
|
1443
|
+
expect(increment2).not.toBeNull()
|
|
1444
|
+
const calls2 = includeIncrement([call], increment2!, extension.sessions)
|
|
1445
|
+
const tx2 = await buildAndSignCall(wallet, sessionManager, calls2, provider, chainId)
|
|
1446
|
+
await simulateTransaction(provider, tx2)
|
|
1447
|
+
},
|
|
1448
|
+
timeout,
|
|
1449
|
+
)
|
|
1450
|
+
|
|
1451
|
+
describe('increment built from non-increment calls only', () => {
|
|
1452
|
+
it(
|
|
1453
|
+
'prepareIncrement returns null when calls contain only an increment call (no non-increment calls)',
|
|
1454
|
+
async () => {
|
|
1455
|
+
const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
|
|
1456
|
+
const chainId = Number(await provider.request({ method: 'eth_chainId' }))
|
|
1457
|
+
|
|
1458
|
+
const identityPrivateKey = Secp256k1.randomPrivateKey()
|
|
1459
|
+
const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey }))
|
|
1460
|
+
const stateProvider = new State.Local.Provider()
|
|
1461
|
+
|
|
1462
|
+
const sessionPermission: ExplicitSessionConfig = {
|
|
1463
|
+
chainId,
|
|
1464
|
+
valueLimit: 0n,
|
|
1465
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
1466
|
+
permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()],
|
|
1467
|
+
}
|
|
1468
|
+
const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission)
|
|
1469
|
+
const sessionTopology = SessionConfig.addExplicitSession(
|
|
1470
|
+
SessionConfig.emptySessionsTopology(identityAddress),
|
|
1471
|
+
{
|
|
1472
|
+
...sessionPermission,
|
|
1473
|
+
signer: explicitSigner.address,
|
|
1474
|
+
},
|
|
1475
|
+
)
|
|
1476
|
+
await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1477
|
+
const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1478
|
+
|
|
1479
|
+
const wallet = await Wallet.fromConfiguration(
|
|
1480
|
+
{
|
|
1481
|
+
threshold: 1n,
|
|
1482
|
+
checkpoint: 0n,
|
|
1483
|
+
topology: [
|
|
1484
|
+
{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash },
|
|
1485
|
+
Hex.random(32),
|
|
1486
|
+
],
|
|
1487
|
+
},
|
|
1488
|
+
{ stateProvider },
|
|
1489
|
+
)
|
|
1490
|
+
const sessionManager = new Signers.SessionManager(wallet, {
|
|
1491
|
+
provider,
|
|
1492
|
+
sessionManagerAddress: extension.sessions,
|
|
1493
|
+
explicitSigners: [explicitSigner],
|
|
1494
|
+
})
|
|
1495
|
+
|
|
1496
|
+
// Only an increment call (no non-increment calls). Contract would reject payload with only self-call; we return null.
|
|
1497
|
+
const incrementOnlyCall: Payload.Call = {
|
|
1498
|
+
to: extension.sessions,
|
|
1499
|
+
data: AbiFunction.encodeData(Constants.INCREMENT_USAGE_LIMIT, [[]]),
|
|
1500
|
+
value: 0n,
|
|
1501
|
+
gasLimit: 0n,
|
|
1502
|
+
delegateCall: false,
|
|
1503
|
+
onlyFallback: false,
|
|
1504
|
+
behaviorOnError: 'revert',
|
|
1505
|
+
}
|
|
1506
|
+
const result = await sessionManager.prepareIncrement(wallet.address, chainId, [incrementOnlyCall])
|
|
1507
|
+
expect(result).toBeNull()
|
|
1508
|
+
},
|
|
1509
|
+
timeout,
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
it(
|
|
1513
|
+
'prepareIncrement([increment, nonIncrementCall]) produces same increment data as prepareIncrement([nonIncrementCall])',
|
|
1514
|
+
async () => {
|
|
1515
|
+
const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
|
|
1516
|
+
const chainId = Number(await provider.request({ method: 'eth_chainId' }))
|
|
1517
|
+
|
|
1518
|
+
const identityPrivateKey = Secp256k1.randomPrivateKey()
|
|
1519
|
+
const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey }))
|
|
1520
|
+
const stateProvider = new State.Local.Provider()
|
|
1521
|
+
|
|
1522
|
+
const sessionPermission: ExplicitSessionConfig = {
|
|
1523
|
+
chainId,
|
|
1524
|
+
valueLimit: 0n,
|
|
1525
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
1526
|
+
permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()],
|
|
1527
|
+
}
|
|
1528
|
+
const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission)
|
|
1529
|
+
const sessionTopology = SessionConfig.addExplicitSession(
|
|
1530
|
+
SessionConfig.emptySessionsTopology(identityAddress),
|
|
1531
|
+
{
|
|
1532
|
+
...sessionPermission,
|
|
1533
|
+
signer: explicitSigner.address,
|
|
1534
|
+
},
|
|
1535
|
+
)
|
|
1536
|
+
await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1537
|
+
const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1538
|
+
|
|
1539
|
+
const wallet = await Wallet.fromConfiguration(
|
|
1540
|
+
{
|
|
1541
|
+
threshold: 1n,
|
|
1542
|
+
checkpoint: 0n,
|
|
1543
|
+
topology: [
|
|
1544
|
+
{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash },
|
|
1545
|
+
Hex.random(32),
|
|
1546
|
+
],
|
|
1547
|
+
},
|
|
1548
|
+
{ stateProvider },
|
|
1549
|
+
)
|
|
1550
|
+
const sessionManager = new Signers.SessionManager(wallet, {
|
|
1551
|
+
provider,
|
|
1552
|
+
sessionManagerAddress: extension.sessions,
|
|
1553
|
+
explicitSigners: [explicitSigner],
|
|
1554
|
+
})
|
|
1555
|
+
|
|
1556
|
+
const nonIncrementCall: Payload.Call = {
|
|
1557
|
+
to: EMITTER_ADDRESS1,
|
|
1558
|
+
value: 0n,
|
|
1559
|
+
data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]),
|
|
1560
|
+
gasLimit: 0n,
|
|
1561
|
+
delegateCall: false,
|
|
1562
|
+
onlyFallback: false,
|
|
1563
|
+
behaviorOnError: 'revert',
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
const fromNonIncrementOnly = await sessionManager.prepareIncrement(wallet.address, chainId, [
|
|
1567
|
+
nonIncrementCall,
|
|
1568
|
+
])
|
|
1569
|
+
expect(fromNonIncrementOnly).not.toBeNull()
|
|
1570
|
+
|
|
1571
|
+
// Pass [staleIncrement, nonIncrementCall] — increment is ignored when building; result should match
|
|
1572
|
+
const withStaleIncrement = await sessionManager.prepareIncrement(wallet.address, chainId, [
|
|
1573
|
+
fromNonIncrementOnly!,
|
|
1574
|
+
nonIncrementCall,
|
|
1575
|
+
])
|
|
1576
|
+
expect(withStaleIncrement).not.toBeNull()
|
|
1577
|
+
expect(withStaleIncrement!.data).toEqual(fromNonIncrementOnly!.data)
|
|
1578
|
+
},
|
|
1579
|
+
timeout,
|
|
1580
|
+
)
|
|
1581
|
+
|
|
1582
|
+
it(
|
|
1583
|
+
'payload with implicit and explicit: increment built only from explicit non-increment call',
|
|
1584
|
+
async () => {
|
|
1585
|
+
const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
|
|
1586
|
+
const chainId = Number(await provider.request({ method: 'eth_chainId' }))
|
|
1587
|
+
|
|
1588
|
+
const identityPrivateKey = Secp256k1.randomPrivateKey()
|
|
1589
|
+
const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey }))
|
|
1590
|
+
const stateProvider = new State.Local.Provider()
|
|
1591
|
+
|
|
1592
|
+
const redirectUrl = 'https://example.com'
|
|
1593
|
+
const implicitSigner = await createImplicitSigner(redirectUrl, identityPrivateKey)
|
|
1594
|
+
const sessionPermission: ExplicitSessionConfig = {
|
|
1595
|
+
chainId,
|
|
1596
|
+
valueLimit: 0n,
|
|
1597
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
1598
|
+
permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()],
|
|
1599
|
+
}
|
|
1600
|
+
const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission)
|
|
1601
|
+
|
|
1602
|
+
// Topology: identity + blacklist (implicit) + explicit session
|
|
1603
|
+
let sessionTopology = SessionConfig.emptySessionsTopology(identityAddress)
|
|
1604
|
+
sessionTopology = SessionConfig.addExplicitSession(sessionTopology, {
|
|
1605
|
+
...sessionPermission,
|
|
1606
|
+
signer: explicitSigner.address,
|
|
1607
|
+
})
|
|
1608
|
+
await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1609
|
+
const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1610
|
+
|
|
1611
|
+
const wallet = await Wallet.fromConfiguration(
|
|
1612
|
+
{
|
|
1613
|
+
threshold: 1n,
|
|
1614
|
+
checkpoint: 0n,
|
|
1615
|
+
topology: [
|
|
1616
|
+
{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash },
|
|
1617
|
+
Hex.random(32),
|
|
1618
|
+
],
|
|
1619
|
+
},
|
|
1620
|
+
{ stateProvider },
|
|
1621
|
+
)
|
|
1622
|
+
const sessionManager = new Signers.SessionManager(wallet, {
|
|
1623
|
+
provider,
|
|
1624
|
+
sessionManagerAddress: extension.sessions,
|
|
1625
|
+
implicitSigners: [implicitSigner],
|
|
1626
|
+
explicitSigners: [explicitSigner],
|
|
1627
|
+
})
|
|
1628
|
+
|
|
1629
|
+
// Explicit call only (onlyOnce produces an increment; implicit does not match this target)
|
|
1630
|
+
const call: Payload.Call = {
|
|
1631
|
+
to: EMITTER_ADDRESS1,
|
|
1632
|
+
value: 0n,
|
|
1633
|
+
data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]),
|
|
1634
|
+
gasLimit: 0n,
|
|
1635
|
+
delegateCall: false,
|
|
1636
|
+
onlyFallback: false,
|
|
1637
|
+
behaviorOnError: 'revert',
|
|
1638
|
+
}
|
|
1639
|
+
const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call])
|
|
1640
|
+
expect(increment).not.toBeNull()
|
|
1641
|
+
const calls = includeIncrement([call], increment!, extension.sessions)
|
|
1642
|
+
const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId)
|
|
1643
|
+
await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[0])
|
|
1644
|
+
},
|
|
1645
|
+
timeout,
|
|
1646
|
+
)
|
|
1647
|
+
|
|
1648
|
+
it('signSapient handles selector-only self increment call consistently', async () => {
|
|
1649
|
+
const identityPrivateKey = Secp256k1.randomPrivateKey()
|
|
1650
|
+
const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey }))
|
|
1651
|
+
const stateProvider = new State.Local.Provider()
|
|
1652
|
+
|
|
1653
|
+
const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), {
|
|
1654
|
+
chainId: 0,
|
|
1655
|
+
valueLimit: 0n,
|
|
1656
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
1657
|
+
permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()],
|
|
1658
|
+
})
|
|
1659
|
+
|
|
1660
|
+
const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), {
|
|
1661
|
+
...explicitSigner.sessionPermissions,
|
|
1662
|
+
signer: explicitSigner.address,
|
|
1663
|
+
})
|
|
1664
|
+
await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1665
|
+
const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology))
|
|
1666
|
+
|
|
1667
|
+
const wallet = await Wallet.fromConfiguration(
|
|
1668
|
+
{
|
|
1669
|
+
threshold: 1n,
|
|
1670
|
+
checkpoint: 0n,
|
|
1671
|
+
topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)],
|
|
1672
|
+
},
|
|
1673
|
+
{ stateProvider },
|
|
1674
|
+
)
|
|
1675
|
+
const sessionManager = new Signers.SessionManager(wallet, {
|
|
1676
|
+
sessionManagerAddress: extension.sessions,
|
|
1677
|
+
explicitSigners: [explicitSigner],
|
|
1678
|
+
})
|
|
1679
|
+
|
|
1680
|
+
const payload: Payload.Parented = {
|
|
1681
|
+
type: 'call',
|
|
1682
|
+
nonce: 0n,
|
|
1683
|
+
space: 0n,
|
|
1684
|
+
calls: [
|
|
1685
|
+
{
|
|
1686
|
+
to: extension.sessions,
|
|
1687
|
+
data: AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT),
|
|
1688
|
+
value: 0n,
|
|
1689
|
+
gasLimit: 0n,
|
|
1690
|
+
delegateCall: false,
|
|
1691
|
+
onlyFallback: false,
|
|
1692
|
+
behaviorOnError: 'revert',
|
|
1693
|
+
},
|
|
1694
|
+
],
|
|
1695
|
+
parentWallets: [wallet.address],
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
const signature = await sessionManager.signSapient(wallet.address, 0, payload, imageHash)
|
|
1699
|
+
expect(signature.type).toBe('sapient')
|
|
1700
|
+
})
|
|
1701
|
+
})
|
|
1360
1702
|
})
|
|
1361
1703
|
}
|