@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.
@@ -1,5 +1,5 @@
1
1
 
2
2
 
3
- > @0xsequence/wallet-core@3.0.0 build /home/taylan/development/sequence/sequence.js/packages/wallet/core
3
+ > @0xsequence/wallet-core@3.0.2 build /home/taylan/development/sequence/sequence.js/packages/wallet/core
4
4
  > tsc
5
5
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @0xsequence/wallet-core@3.0.0 lint /home/taylan/development/sequence/sequence.js/packages/wallet/core
2
+ > @0xsequence/wallet-core@3.0.2 lint /home/taylan/development/sequence/sequence.js/packages/wallet/core
3
3
  > eslint . --max-warnings 0
4
4
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @0xsequence/wallet-core@3.0.0 typecheck /home/taylan/development/sequence/sequence.js/packages/wallet/core
2
+ > @0xsequence/wallet-core@3.0.2 typecheck /home/taylan/development/sequence/sequence.js/packages/wallet/core
3
3
  > tsc --noEmit
4
4
 
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,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEvF,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;IAiBb,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;YAiCnC,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
+ {"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 (Address.isEqual(call.to, sessionManagerAddress) &&
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 (Address.isEqual(call.to, sessionManagerAddress) &&
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,EAAE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AACxF,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AAE3C,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"}
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,EAG3B,MAAM,oBAAoB,CAAA;AAE3B,MAAM,MAAM,qBAAqB,GAAG;IAClC,qBAAqB,EAAE,OAAO,CAAC,OAAO,CAAA;IACtC,aAAa,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAA;IAC9B,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAA;IAC5B,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAA;CAC7B,CAAA;AAID,qBAAa,cAAe,YAAW,aAAa;IAShD,QAAQ,CAAC,MAAM,EAAE,MAAM;IARzB,SAAgB,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAA;IAC7C,SAAgB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAA;IAExC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAY;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAY;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAmB;gBAGnC,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,qBAAqB;IAShC,IAAI,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC,CAE5C;IAEK,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC;IASlD,IAAI,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAEtD;IAEK,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC;IAY5D,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,GAAG,cAAc;IAUzD,kBAAkB,CAAC,MAAM,EAAE,QAAQ,GAAG,cAAc;IAWpD,kBAAkB,CAAC,MAAM,EAAE,QAAQ,GAAG,cAAc;IAY9C,kBAAkB,CACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,0BAA0B,CAAA;KAAE,EAAE,CAAC;IAgBjG,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAmD9G,gBAAgB,CACpB,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,GACpB,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAmDzB,WAAW,CACf,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,QAAQ,EACzB,SAAS,EAAE,GAAG,CAAC,GAAG,GACjB,OAAO,CAAC,cAAc,CAAC,4BAA4B,CAAC;IAgHjD,uBAAuB,CAC3B,MAAM,EAAE,OAAO,CAAC,OAAO,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,QAAQ,EACzB,SAAS,EAAE,cAAc,CAAC,4BAA4B,GACrD,OAAO,CAAC,OAAO,CAAC;CAsCpB"}
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
- async findSignersForCalls(wallet, chainId, calls) {
90
- // Only use signers that match the topology
91
- const topology = await this.topology;
92
- const identitySigners = SessionConfig.getIdentitySigners(topology);
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. ` + `Call: to=${call.to}, data=${call.data}, value=${call.value}, `);
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
- // Create a map of signers to their associated calls
142
- const signerToCalls = new Map();
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
- const existingCalls = signerToCalls.get(signer) || [];
146
- signerToCalls.set(signer, [...existingCalls, call]);
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 with their associated calls
149
- const increments = (await Promise.all(Array.from(signerToCalls.entries()).map(async ([signer, associatedCalls]) => {
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, associatedCalls, this.address, this._provider);
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.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/guard": "^3.0.0",
31
- "@0xsequence/wallet-primitives": "^3.0.0",
32
- "@0xsequence/relayer": "^3.0.0"
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
- async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise<SessionSigner[]> {
134
- // Only use signers that match the topology
135
- const topology = await this.topology
136
- const identitySigners = SessionConfig.getIdentitySigners(topology)
137
- if (identitySigners.length === 0) {
138
- throw new Error('Identity signers not found')
139
- }
140
-
141
- // Prioritize implicit signers
142
- const availableSigners = [...this._implicitSigners, ...this._explicitSigners]
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
- `No signer supported for call. ` + `Call: to=${call.to}, data=${call.data}, value=${call.value}, `,
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
- // Create a map of signers to their associated calls
195
- const signerToCalls = new Map<SessionSigner, Payload.Call[]>()
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
- const existingCalls = signerToCalls.get(signer) || []
199
- signerToCalls.set(signer, [...existingCalls, call])
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 with their associated calls
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(signerToCalls.entries()).map(async ([signer, associatedCalls]) => {
262
+ Array.from(signerToNonIncrementCalls.entries()).map(async ([signer, nonIncrementCalls]) => {
206
263
  if (isExplicitSessionSigner(signer)) {
207
- return signer.prepareIncrements(wallet, chainId, associatedCalls, this.address, this._provider!)
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 a payload using an explicit session',
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
  }