@aastar/sdk 0.20.8 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BaseClient-CkBhQ1ou.d.cts +88 -0
- package/dist/UserClient-BDGP37PK.js +6 -0
- package/dist/{UserClient-AIIHB54I.js.map → UserClient-BDGP37PK.js.map} +1 -1
- package/dist/UserClient-FOAOBLGK.cjs +15 -0
- package/dist/UserClient-FOAOBLGK.cjs.map +1 -0
- package/dist/account.cjs +31 -0
- package/dist/account.cjs.map +1 -0
- package/dist/account.d.cts +48 -0
- package/dist/account.js +3 -3
- package/dist/admin.cjs +15 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +62 -0
- package/dist/admin.js +3 -3
- package/dist/airaccount.cjs +452 -0
- package/dist/airaccount.cjs.map +1 -0
- package/dist/airaccount.d.cts +4 -0
- package/dist/airaccount.js +3 -3
- package/dist/channel-CkRRbzT8.d.cts +77 -0
- package/dist/channel.cjs +27 -0
- package/dist/channel.cjs.map +1 -0
- package/dist/channel.d.cts +64 -0
- package/dist/channel.js +3 -3
- package/dist/chunk-3HZEIFBW.cjs +118 -0
- package/dist/chunk-3HZEIFBW.cjs.map +1 -0
- package/dist/{chunk-7ARJ3OSU.js → chunk-57XLR2NT.js} +3 -3
- package/dist/{chunk-7ARJ3OSU.js.map → chunk-57XLR2NT.js.map} +1 -1
- package/dist/chunk-5JFYTJOE.cjs +448 -0
- package/dist/chunk-5JFYTJOE.cjs.map +1 -0
- package/dist/{chunk-KDH3UPKD.js → chunk-5NKU5NT5.js} +8 -8
- package/dist/{chunk-KDH3UPKD.js.map → chunk-5NKU5NT5.js.map} +1 -1
- package/dist/chunk-63JM67L7.cjs +435 -0
- package/dist/chunk-63JM67L7.cjs.map +1 -0
- package/dist/{chunk-IC3G6YM2.js → chunk-6OWZOTE7.js} +113 -9
- package/dist/chunk-6OWZOTE7.js.map +1 -0
- package/dist/chunk-CIEYY3A6.cjs +43259 -0
- package/dist/chunk-CIEYY3A6.cjs.map +1 -0
- package/dist/chunk-DEUBKZH5.cjs +421 -0
- package/dist/chunk-DEUBKZH5.cjs.map +1 -0
- package/dist/{chunk-TENYCMJ3.js → chunk-DF4WVR2H.js} +9 -9
- package/dist/{chunk-TENYCMJ3.js.map → chunk-DF4WVR2H.js.map} +1 -1
- package/dist/{chunk-BN5WY5GM.js → chunk-DQBKE4ND.js} +4 -4
- package/dist/{chunk-BN5WY5GM.js.map → chunk-DQBKE4ND.js.map} +1 -1
- package/dist/{chunk-4EZD7LPE.js → chunk-E4CQFW75.js} +3 -3
- package/dist/{chunk-4EZD7LPE.js.map → chunk-E4CQFW75.js.map} +1 -1
- package/dist/{chunk-LXWIPTPX.js → chunk-ENSMYCU6.js} +3 -3
- package/dist/{chunk-LXWIPTPX.js.map → chunk-ENSMYCU6.js.map} +1 -1
- package/dist/{chunk-FJ7XECC5.js → chunk-EY2AJTGV.js} +3 -3
- package/dist/{chunk-FJ7XECC5.js.map → chunk-EY2AJTGV.js.map} +1 -1
- package/dist/chunk-GAMSWXWI.cjs +1168 -0
- package/dist/chunk-GAMSWXWI.cjs.map +1 -0
- package/dist/chunk-IJN776TA.cjs +585 -0
- package/dist/chunk-IJN776TA.cjs.map +1 -0
- package/dist/{chunk-PKCHRXFR.js → chunk-KZERVPUR.js} +3 -3
- package/dist/{chunk-PKCHRXFR.js.map → chunk-KZERVPUR.js.map} +1 -1
- package/dist/{chunk-6QYXGMCR.js → chunk-M5WFKETT.js} +833 -47
- package/dist/chunk-M5WFKETT.js.map +1 -0
- package/dist/chunk-M7HXR7G5.cjs +128 -0
- package/dist/chunk-M7HXR7G5.cjs.map +1 -0
- package/dist/{chunk-PAABYXS6.js → chunk-MCALA6WM.js} +6 -6
- package/dist/{chunk-PAABYXS6.js.map → chunk-MCALA6WM.js.map} +1 -1
- package/dist/chunk-MXJEULSE.cjs +396 -0
- package/dist/chunk-MXJEULSE.cjs.map +1 -0
- package/dist/chunk-OVNOSAL3.cjs +4797 -0
- package/dist/chunk-OVNOSAL3.cjs.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/{chunk-MVEWJIPY.js → chunk-RXPSL33E.js} +3 -3
- package/dist/{chunk-MVEWJIPY.js.map → chunk-RXPSL33E.js.map} +1 -1
- package/dist/{chunk-G3UJC4EL.js → chunk-UCLK6LTB.js} +39 -36
- package/dist/chunk-UCLK6LTB.js.map +1 -0
- package/dist/chunk-WR4OZUXR.cjs +115 -0
- package/dist/chunk-WR4OZUXR.cjs.map +1 -0
- package/dist/chunk-XQROKLZI.cjs +4521 -0
- package/dist/chunk-XQROKLZI.cjs.map +1 -0
- package/dist/chunk-Y4EJX7UA.cjs +228 -0
- package/dist/chunk-Y4EJX7UA.cjs.map +1 -0
- package/dist/chunk-Z4GZ6DQA.cjs +108 -0
- package/dist/chunk-Z4GZ6DQA.cjs.map +1 -0
- package/dist/contract-addresses-RABD77VP.cjs +49 -0
- package/dist/contract-addresses-RABD77VP.cjs.map +1 -0
- package/dist/{contract-addresses-N3TOL2WL.js → contract-addresses-TVXSRQ7I.js} +3 -3
- package/dist/{contract-addresses-N3TOL2WL.js.map → contract-addresses-TVXSRQ7I.js.map} +1 -1
- package/dist/core.cjs +898 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +7048 -0
- package/dist/core.d.ts +130 -12
- package/dist/core.js +2 -2
- package/dist/dapp.cjs +289 -0
- package/dist/dapp.cjs.map +1 -0
- package/dist/dapp.d.cts +127 -0
- package/dist/dapp.js +3 -3
- package/dist/doc-types-471vSmPO.d.cts +16 -0
- package/dist/enduser.cjs +24 -0
- package/dist/enduser.cjs.map +1 -0
- package/dist/enduser.d.cts +261 -0
- package/dist/enduser.js +4 -4
- package/dist/identity.cjs +23 -0
- package/dist/identity.cjs.map +1 -0
- package/dist/identity.d.cts +81 -0
- package/dist/identity.js +3 -3
- package/dist/index-B6SfEQxo.d.cts +47 -0
- package/dist/index.cjs +2818 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +656 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -17
- package/dist/kms.cjs +452 -0
- package/dist/kms.cjs.map +1 -0
- package/dist/kms.d.cts +3104 -0
- package/dist/kms.d.ts +62 -12
- package/dist/kms.js +3 -3
- package/dist/lib-FE4GR7TO.cjs +1865 -0
- package/dist/lib-FE4GR7TO.cjs.map +1 -0
- package/dist/operator.cjs +27 -0
- package/dist/operator.cjs.map +1 -0
- package/dist/operator.d.cts +164 -0
- package/dist/operator.js +3 -3
- package/dist/paymaster.cjs +63 -0
- package/dist/paymaster.cjs.map +1 -0
- package/dist/paymaster.d.cts +312 -0
- package/dist/paymaster.js +3 -3
- package/dist/src-CUHI6G6W.cjs +898 -0
- package/dist/src-CUHI6G6W.cjs.map +1 -0
- package/dist/src-KHCWIS4Q.cjs +63 -0
- package/dist/src-KHCWIS4Q.cjs.map +1 -0
- package/dist/{src-X5MIV3EB.js → src-RSN4U2T2.js} +5 -5
- package/dist/src-RSN4U2T2.js.map +1 -0
- package/dist/{src-L5SI5WNB.js → src-XCV6BTSV.js} +4 -4
- package/dist/{src-X5MIV3EB.js.map → src-XCV6BTSV.js.map} +1 -1
- package/dist/tier-router-DeeVg69O.d.cts +370 -0
- package/dist/tokens.cjs +15 -0
- package/dist/tokens.cjs.map +1 -0
- package/dist/tokens.d.cts +64 -0
- package/dist/tokens.js +3 -3
- package/dist/x402.cjs +103 -0
- package/dist/x402.cjs.map +1 -0
- package/dist/x402.d.cts +373 -0
- package/dist/x402.js +3 -3
- package/package.json +116 -32
- package/dist/UserClient-AIIHB54I.js +0 -6
- package/dist/chunk-6QYXGMCR.js.map +0 -1
- package/dist/chunk-G3UJC4EL.js.map +0 -1
- package/dist/chunk-IC3G6YM2.js.map +0 -1
- package/dist/src-L5SI5WNB.js.map +0 -1
|
@@ -0,0 +1,4797 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkXQROKLZI_cjs = require('./chunk-XQROKLZI.cjs');
|
|
4
|
+
var chunkMXJEULSE_cjs = require('./chunk-MXJEULSE.cjs');
|
|
5
|
+
var viem = require('viem');
|
|
6
|
+
var axios = require('axios');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
var accounts = require('viem/accounts');
|
|
9
|
+
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
|
|
12
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
13
|
+
|
|
14
|
+
// ../airaccount/src/server/constants/entrypoint.ts
|
|
15
|
+
var CORE_SEPOLIA = chunkMXJEULSE_cjs.CANONICAL_ADDRESSES[11155111];
|
|
16
|
+
var EntryPointVersion = /* @__PURE__ */ ((EntryPointVersion2) => {
|
|
17
|
+
EntryPointVersion2["V0_6"] = "0.6";
|
|
18
|
+
EntryPointVersion2["V0_7"] = "0.7";
|
|
19
|
+
EntryPointVersion2["V0_8"] = "0.8";
|
|
20
|
+
return EntryPointVersion2;
|
|
21
|
+
})(EntryPointVersion || {});
|
|
22
|
+
var ENTRYPOINT_ADDRESSES = {
|
|
23
|
+
["0.6" /* V0_6 */]: {
|
|
24
|
+
sepolia: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
25
|
+
mainnet: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
26
|
+
optimism: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
|
|
27
|
+
},
|
|
28
|
+
["0.7" /* V0_7 */]: {
|
|
29
|
+
sepolia: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
|
30
|
+
mainnet: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
|
31
|
+
optimism: "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
|
|
32
|
+
},
|
|
33
|
+
["0.8" /* V0_8 */]: {
|
|
34
|
+
sepolia: "0x0576a174D229E3cFA37253523E645A78A0C91B57",
|
|
35
|
+
mainnet: "0x0576a174D229E3cFA37253523E645A78A0C91B57",
|
|
36
|
+
optimism: "0x0576a174D229E3cFA37253523E645A78A0C91B57"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var ENTRYPOINT_ABI_V6 = [
|
|
40
|
+
"function simulateValidation((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes) userOp) external",
|
|
41
|
+
"function getNonce(address sender, uint192 key) external view returns (uint256 nonce)",
|
|
42
|
+
"function getUserOpHash((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes) userOp) external view returns (bytes32)",
|
|
43
|
+
"function handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[] ops, address payable beneficiary) external"
|
|
44
|
+
];
|
|
45
|
+
var ENTRYPOINT_ABI_V7_V8 = [
|
|
46
|
+
"function simulateValidation((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes) packedUserOp) external",
|
|
47
|
+
"function getNonce(address sender, uint192 key) external view returns (uint256 nonce)",
|
|
48
|
+
"function getUserOpHash((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes) packedUserOp) external view returns (bytes32)",
|
|
49
|
+
"function handleOps((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes)[] ops, address payable beneficiary) external"
|
|
50
|
+
];
|
|
51
|
+
var FACTORY_ABI_V6 = [
|
|
52
|
+
"function getAddress(address creator, address signer, address validator, bool useAAStarValidator, uint256 salt) view returns (address)",
|
|
53
|
+
"function createAccountWithAAStarValidator(address creator, address signer, address aaStarValidator, bool useAAStarValidator, uint256 salt) returns (address)"
|
|
54
|
+
];
|
|
55
|
+
var FACTORY_ABI_V7_V8 = [
|
|
56
|
+
"function getAddress(address creator, address signer, address validator, bool useAAStarValidator, uint256 salt) view returns (address)",
|
|
57
|
+
"function createAccount(address creator, address signer, address aaStarValidator, bool useAAStarValidator, uint256 salt) returns (address)"
|
|
58
|
+
];
|
|
59
|
+
var ACCOUNT_ABI = [
|
|
60
|
+
"function execute(address dest, uint256 value, bytes calldata func) external"
|
|
61
|
+
];
|
|
62
|
+
var VALIDATOR_ABI = [
|
|
63
|
+
"function getGasEstimate(uint256 nodeCount) external pure returns (uint256 gasEstimate)"
|
|
64
|
+
];
|
|
65
|
+
var AIRACCOUNT_ADDRESSES = {
|
|
66
|
+
sepolia: {
|
|
67
|
+
// M4 factory (legacy — 3-field InitConfig)
|
|
68
|
+
factoryM4: "0x914db0a849f55e68a726c72fd02b7114b1176d88",
|
|
69
|
+
// M5 factory r5 — 6-field InitConfig, guardian acceptance sigs required
|
|
70
|
+
factoryM5: "0xd72a236d84be6c388a8bc7deb64afd54704ae385",
|
|
71
|
+
/** @deprecated defaultCommunityGuardian was address(0); superseded by r6 and r4. Do not use for new accounts. */
|
|
72
|
+
factoryM7r5Prev: "0xa0007c5dB27548D8c1582773856dB1D123107383",
|
|
73
|
+
// ── Deprecated: r6 addresses (2026-03-29 deployment, superseded by r4 audit-final) ──────────
|
|
74
|
+
// Retain for legacy account lookups and historical event indexing ONLY.
|
|
75
|
+
// DO NOT use for new account creation — CREATE2 address will differ from r4.
|
|
76
|
+
/** @deprecated Use {@link factory} (r4 audit-final) for new accounts. */
|
|
77
|
+
factoryM7r6: "0x42f82d77f9cf940686b6a64a369245cb563e0e85",
|
|
78
|
+
/** @deprecated Use {@link accountImpl} (r4 audit-final). */
|
|
79
|
+
accountImplM7r6: "0x2F1B4EB63143D338bE78d0AF878B806f075080c1",
|
|
80
|
+
/** @deprecated Use {@link compositeValidator} (r4 audit-final). */
|
|
81
|
+
compositeValidatorM7r6: "0x4135c539fec5e200fe9762b721f6829b2315cbe1",
|
|
82
|
+
/** @deprecated Use {@link tierGuardHook} (r4 audit-final). */
|
|
83
|
+
tierGuardHookM7r6: "0x73572e9e6138fd53465ee243e2fb4842cf86a787",
|
|
84
|
+
/** @deprecated Use {@link agentSessionKeyValidator} (r4 audit-final). */
|
|
85
|
+
agentSessionKeyValidatorM7r6: "0xa3e52db4b6e0a9d7cd5dd1414a90eedcf950e029",
|
|
86
|
+
// ── Deprecated: r4 audit-final (v0.16.0 era — pre-beta). Retained for existing account recovery. ─
|
|
87
|
+
/** @deprecated Use factory (beta.4) for new accounts. */
|
|
88
|
+
factoryM7r4: "0x61bBAf9E1b8Fd78fF874776cFa50497dB9d43C3F",
|
|
89
|
+
/** @deprecated */
|
|
90
|
+
accountImplM7r4: "0xA674D308ce22230B70412b20Ee5a66fC6B24F49c",
|
|
91
|
+
/** @deprecated Use validatorRouter. */
|
|
92
|
+
validatorRouterM7r4: "0x730a162Ce3202b94cC5B74181B75b11eBB3045B1",
|
|
93
|
+
/** @deprecated */
|
|
94
|
+
compositeValidatorM7r4: "0xB65569950C48AA56dbe876915ca3605fD6FF2980",
|
|
95
|
+
/** @deprecated */
|
|
96
|
+
tierGuardHookM7r4: "0x67f878295cFF7451CBD2A775C4490607AF1b07d7",
|
|
97
|
+
/** @deprecated */
|
|
98
|
+
agentSessionKeyValidatorM7r4: "0x1F06961e133217801F92e1CF552187F594a32873",
|
|
99
|
+
// ── Current: derived from @aastar/core CANONICAL_ADDRESSES[11155111] ───────────
|
|
100
|
+
// SINGLE SOURCE OF TRUTH. Do NOT hand-copy hex here. core is synced on every
|
|
101
|
+
// protocol redeploy (currently AirAccount v0.20.0, Sepolia 2026-06-20),
|
|
102
|
+
// and these fields re-derive automatically. The key mapping (airaccount field ←
|
|
103
|
+
// core key) is asserted by entrypoint.addresses.test.ts so it can't silently drift.
|
|
104
|
+
factory: CORE_SEPOLIA.airAccountFactoryV7,
|
|
105
|
+
factoryM7: CORE_SEPOLIA.airAccountFactoryV7,
|
|
106
|
+
accountImpl: CORE_SEPOLIA.airAccountV7Impl,
|
|
107
|
+
validatorRouter: CORE_SEPOLIA.aaStarValidator,
|
|
108
|
+
blsAlgorithm: CORE_SEPOLIA.aaStarBLSAlgorithm,
|
|
109
|
+
blsAggregator: CORE_SEPOLIA.aaStarBLSAggregator,
|
|
110
|
+
// SuperPaymaster proxy — same concept as core's `superPaymaster` proxy.
|
|
111
|
+
superPaymaster: CORE_SEPOLIA.superPaymaster,
|
|
112
|
+
sessionKeyValidator: CORE_SEPOLIA.sessionKeyValidator,
|
|
113
|
+
forceExitModule: CORE_SEPOLIA.forceExitModule,
|
|
114
|
+
airAccountDelegate: CORE_SEPOLIA.airAccountDelegate,
|
|
115
|
+
airAccountExtension: CORE_SEPOLIA.airAccountExtension,
|
|
116
|
+
agentRegistry: CORE_SEPOLIA.agentRegistry,
|
|
117
|
+
calldataParserRegistry: CORE_SEPOLIA.calldataParserRegistry,
|
|
118
|
+
// uniswapV3Parser is airaccount-specific (not in core) — keep hardcoded.
|
|
119
|
+
uniswapV3Parser: "0x5671810ac8aa1857397870e60232579cfc519515"
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var AIRACCOUNT_ABI = [
|
|
123
|
+
// ── Core execution ──
|
|
124
|
+
"function execute(address dest, uint256 value, bytes calldata func) external",
|
|
125
|
+
"function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external",
|
|
126
|
+
// ── ERC-7579 Module Management (M7.2) ──
|
|
127
|
+
"function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external",
|
|
128
|
+
"function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external",
|
|
129
|
+
"function executeFromExecutor(bytes32 mode, bytes calldata executionCalldata) external returns (bytes[] memory returnData)",
|
|
130
|
+
// ── ERC-7579 Introspection ──
|
|
131
|
+
"function accountId() external pure returns (string memory)",
|
|
132
|
+
"function supportsModule(uint256 moduleTypeId) external pure returns (bool)",
|
|
133
|
+
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view returns (bool)",
|
|
134
|
+
// ── ERC-1271 / ERC-165 ──
|
|
135
|
+
"function isValidSignature(bytes32 hash, bytes calldata sig) external view returns (bytes4)",
|
|
136
|
+
"function validateCompositeSignature(bytes32 hash, bytes calldata sig) external returns (uint256)",
|
|
137
|
+
"function supportsInterface(bytes4 interfaceId) external pure returns (bool)",
|
|
138
|
+
// ── State readers ──
|
|
139
|
+
"function owner() external view returns (address)",
|
|
140
|
+
"function entryPoint() external view returns (address)",
|
|
141
|
+
"function validator() external view returns (address)",
|
|
142
|
+
"function guard() external view returns (address)",
|
|
143
|
+
"function guardianCount() external view returns (uint8)",
|
|
144
|
+
"function guardians(uint256 index) external view returns (address)",
|
|
145
|
+
"function p256KeyX() external view returns (bytes32)",
|
|
146
|
+
"function p256KeyY() external view returns (bytes32)",
|
|
147
|
+
// abitype/viem human-readable ABIs use a bare parenthesised tuple `(...)`, not the
|
|
148
|
+
// ethers-style `tuple(...)` keyword (which parseAbi rejects with "Invalid ABI parameter").
|
|
149
|
+
"function getConfigDescription() external view returns ((address accountOwner, address guardAddress, uint256 dailyLimit, uint256 dailyRemaining, uint256 tier1Limit, uint256 tier2Limit, address[3] guardianAddresses, uint8 guardianCount, bool hasP256Key, bool hasValidator, bool hasAggregator, bool hasActiveRecovery))",
|
|
150
|
+
// ── Owner / key management ──
|
|
151
|
+
"function setValidator(address _validator) external",
|
|
152
|
+
"function setP256Key(bytes32 _x, bytes32 _y) external",
|
|
153
|
+
"function setTierLimits(uint256 _tier1, uint256 _tier2) external",
|
|
154
|
+
"function modifyTierLimitsWithGuardians(uint256 _tier1, uint256 _tier2, uint256 deadline, bytes[] calldata guardianSigs) external",
|
|
155
|
+
// ── Algorithm whitelist (v0.17.2-beta.4: single source of truth on the ACCOUNT, not the guard) ──
|
|
156
|
+
"function approvedAlgorithms(uint8 algId) external view returns (bool)",
|
|
157
|
+
"function guardApproveAlgorithm(uint8 algId) external",
|
|
158
|
+
// ── Social / guardian recovery (F28) ──
|
|
159
|
+
// Guardian set: owner adds guardians (max 3); removal needs RECOVERY_THRESHOLD guardian sigs.
|
|
160
|
+
// Recovery lifecycle: a guardian proposes a new owner (starts the timelock), guardians
|
|
161
|
+
// approve to reach 2-of-3 threshold, then anyone executes after the timelock; guardians
|
|
162
|
+
// (not the owner) can vote to cancel. See RecoveryService for the full flow.
|
|
163
|
+
"function addGuardian(address _guardian) external",
|
|
164
|
+
"function removeGuardian(uint8 index, bytes[] calldata guardianSigs) external",
|
|
165
|
+
"function proposeRecovery(address _newOwner) external",
|
|
166
|
+
"function approveRecovery() external",
|
|
167
|
+
"function cancelRecovery() external",
|
|
168
|
+
"function executeRecovery() external",
|
|
169
|
+
"function activeRecovery() external view returns (address newOwner, uint256 proposedAt, uint256 approvalBitmap, uint256 cancellationBitmap)",
|
|
170
|
+
// ── Weighted-signature governance (algId 0x07) ──
|
|
171
|
+
// WeightConfig tuple = (passkeyWeight, ecdsaWeight, blsWeight, guardian0Weight,
|
|
172
|
+
// guardian1Weight, guardian2Weight, _padding, tier1Threshold, tier2Threshold, tier3Threshold)
|
|
173
|
+
"function setWeightConfig((uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold) config) external",
|
|
174
|
+
"function proposeWeightChange((uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold) proposed) external",
|
|
175
|
+
"function approveWeightChange() external",
|
|
176
|
+
"function cancelWeightChange() external",
|
|
177
|
+
"function executeWeightChange() external",
|
|
178
|
+
"function weightConfig() external view returns (uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold)",
|
|
179
|
+
"function pendingWeightChange() external view returns ((uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold) proposed, uint256 proposedAt, uint256 approvalBitmap)",
|
|
180
|
+
// ── ERC-4337 v0.7 bundler entrypoint (v0.17.2-beta.4) ──
|
|
181
|
+
// Routes a UserOp whose callData starts with the executeUserOp selector to the account,
|
|
182
|
+
// re-deriving the signature algId in-frame (fixes guard-account bundler gas estimation).
|
|
183
|
+
// Only an inner execute()/executeBatch() may be wrapped (else reverts UnsupportedInnerSelector).
|
|
184
|
+
"function executeUserOp((address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature) userOp, bytes32 userOpHash) external",
|
|
185
|
+
// ── Events ──
|
|
186
|
+
"event ModuleInstalled(uint256 indexed moduleTypeId, address indexed module)",
|
|
187
|
+
"event ModuleUninstalled(uint256 indexed moduleTypeId, address indexed module)",
|
|
188
|
+
"event OwnerChanged(address indexed oldOwner, address indexed newOwner)",
|
|
189
|
+
// v0.20.0 (#120): recovery events gained a trailing `uint8 guardianIdx` naming the
|
|
190
|
+
// authorizing guardian slot (on P-256 relayer-submitted paths msg.sender is the relayer,
|
|
191
|
+
// not the guardian). topic0 of these three events changed — decoders must use the new shape.
|
|
192
|
+
"event RecoveryProposed(address indexed newOwner, address indexed proposedBy, uint8 guardianIdx)",
|
|
193
|
+
"event RecoveryApproved(address indexed newOwner, address indexed approvedBy, uint256 approvalCount, uint8 guardianIdx)",
|
|
194
|
+
"event RecoveryCancelVoted(address indexed votedBy, uint256 cancelCount, uint8 guardianIdx)",
|
|
195
|
+
"event RecoveryExecuted(address indexed oldOwner, address indexed newOwner)"
|
|
196
|
+
];
|
|
197
|
+
var AIRACCOUNT_FACTORY_ABI = [
|
|
198
|
+
// Full config creation
|
|
199
|
+
"function createAccount(address owner, uint256 salt, (address[3] guardians, bytes32[3] guardianP256X, bytes32[3] guardianP256Y, uint256 dailyLimit, uint8[] approvedAlgIds, uint256 minDailyLimit, address[] initialTokens, (uint256 tier1Limit, uint256 tier2Limit, uint256 dailyLimit)[] initialTokenConfigs) config) external returns (address)",
|
|
200
|
+
"function getAddress(address owner, uint256 salt, (address[3] guardians, bytes32[3] guardianP256X, bytes32[3] guardianP256Y, uint256 dailyLimit, uint8[] approvedAlgIds, uint256 minDailyLimit, address[] initialTokens, (uint256 tier1Limit, uint256 tier2Limit, uint256 dailyLimit)[] initialTokenConfigs) config) external view returns (address)",
|
|
201
|
+
// Default guardian setup (requires guardian acceptance sigs — M5.3+)
|
|
202
|
+
"function createAccountWithDefaults(address owner, uint256 salt, address guardian1, bytes guardian1Sig, address guardian2, bytes guardian2Sig, uint256 dailyLimit) external returns (address)",
|
|
203
|
+
"function getAddressWithDefaults(address owner, uint256 salt, address guardian1, address guardian2, uint256 dailyLimit) external view returns (address)",
|
|
204
|
+
// Agent-account creation (V7: agentKey + human-guardian2 co-owned account, registered in AgentRegistry)
|
|
205
|
+
"function createAgentAccount(address agentKey, bytes32 agentId, address guardian2, bytes guardian2Sig, bytes agentKeySig, uint48 deadline, uint256 dailyLimit) external returns (address account)",
|
|
206
|
+
"function getAgentAddress(address humanOwner, address agentKey, bytes32 agentId) external view returns (address)",
|
|
207
|
+
"function setAgentRegistry(address _agentRegistry) external",
|
|
208
|
+
"function agentRegistry() external view returns (address)",
|
|
209
|
+
// M7 immutable state
|
|
210
|
+
"function implementation() external view returns (address)",
|
|
211
|
+
"function entryPoint() external view returns (address)",
|
|
212
|
+
"function defaultCommunityGuardian() external view returns (address)",
|
|
213
|
+
"function defaultValidatorModule() external view returns (address)",
|
|
214
|
+
"function defaultHookModule() external view returns (address)",
|
|
215
|
+
// M7.4 ERC-7828 chain-qualified address helpers
|
|
216
|
+
"function getChainQualifiedAddress(address account) external view returns (bytes32)",
|
|
217
|
+
"function getAddressWithChainId(address owner, uint256 salt, (address[3] guardians, bytes32[3] guardianP256X, bytes32[3] guardianP256Y, uint256 dailyLimit, uint8[] approvedAlgIds, uint256 minDailyLimit, address[] initialTokens, (uint256 tier1Limit, uint256 tier2Limit, uint256 dailyLimit)[] initialTokenConfigs) config) external view returns (address account, bytes32 chainQualified)",
|
|
218
|
+
// Events
|
|
219
|
+
"event AccountCreated(address indexed account, address indexed owner, uint256 salt)"
|
|
220
|
+
];
|
|
221
|
+
var GLOBAL_GUARD_ABI = [
|
|
222
|
+
"function remainingDailyAllowance() external view returns (uint256)",
|
|
223
|
+
"function dailyLimit() external view returns (uint256)",
|
|
224
|
+
"function account() external view returns (address)",
|
|
225
|
+
// Spend accounting (record* — algId dropped from the ETH path; kept for per-token tier math)
|
|
226
|
+
"function recordSpend(uint256 value) external returns (bool)",
|
|
227
|
+
"function recordTokenSpend(address token, uint256 amount, uint8 algId) external returns (bool)"
|
|
228
|
+
];
|
|
229
|
+
var ERC20_ABI = [
|
|
230
|
+
"function name() view returns (string)",
|
|
231
|
+
"function symbol() view returns (string)",
|
|
232
|
+
"function decimals() view returns (uint8)",
|
|
233
|
+
"function totalSupply() view returns (uint256)",
|
|
234
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
235
|
+
"function transfer(address to, uint256 amount) returns (bool)",
|
|
236
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
237
|
+
"function approve(address spender, uint256 amount) returns (bool)"
|
|
238
|
+
];
|
|
239
|
+
var AGENT_SESSION_KEY_VALIDATOR_ABI = [
|
|
240
|
+
// ERC-7579 lifecycle
|
|
241
|
+
"function onInstall(bytes calldata data) external",
|
|
242
|
+
"function onUninstall(bytes calldata data) external",
|
|
243
|
+
"function isInitialized(address smartAccount) external view returns (bool)",
|
|
244
|
+
// Session management
|
|
245
|
+
"function grantAgentSession(address sessionKey, (uint48 expiry, uint16 velocityLimit, uint32 velocityWindow, bool revoked, address[] callTargets, bytes4[] selectorAllowlist) cfg) external",
|
|
246
|
+
"function delegateSession(address account, address subKey, (uint48 expiry, uint16 velocityLimit, uint32 velocityWindow, bool revoked, address[] callTargets, bytes4[] selectorAllowlist) subCfg) external",
|
|
247
|
+
"function revokeAgentSession(address sessionKey) external",
|
|
248
|
+
// Validation
|
|
249
|
+
"function validateUserOp((address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature) userOp, bytes32 userOpHash) external returns (uint256 validationData)",
|
|
250
|
+
"function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata data) external pure returns (bytes4)",
|
|
251
|
+
// Enforcement
|
|
252
|
+
"function enforceSessionScope(address account, address sessionKey, address callTarget, bytes4 selector) external view",
|
|
253
|
+
// State readers
|
|
254
|
+
"function agentSessions(address account, address sessionKey) external view returns (uint48 expiry, uint16 velocityLimit, uint32 velocityWindow, bool revoked, address[] memory callTargets, bytes4[] memory selectorAllowlist)",
|
|
255
|
+
"function sessionStates(address account, address sessionKey) external view returns (uint256 callCount, uint256 windowStart)",
|
|
256
|
+
"function sessionKeyOwner(address sessionKey) external view returns (address)",
|
|
257
|
+
"function delegatedBy(address account, address subKey) external view returns (address parentKey)",
|
|
258
|
+
// Events
|
|
259
|
+
"event AgentSessionGranted(address indexed account, address indexed sessionKey, uint48 expiry)",
|
|
260
|
+
"event AgentSessionRevoked(address indexed account, address indexed sessionKey)",
|
|
261
|
+
"event AgentSessionDelegated(address indexed parentAccount, address indexed parentKey, address indexed subKey, uint48 expiry)"
|
|
262
|
+
];
|
|
263
|
+
var TIER_GUARD_HOOK_ABI = [
|
|
264
|
+
// ERC-7579 lifecycle
|
|
265
|
+
"function onInstall(bytes calldata data) external",
|
|
266
|
+
"function onUninstall(bytes calldata data) external",
|
|
267
|
+
"function isInitialized(address smartAccount) external view returns (bool)",
|
|
268
|
+
// ERC-7579 Hook interface
|
|
269
|
+
"function preCheck(address msgSender, uint256 msgValue, bytes calldata msgData) external returns (bytes memory hookData)",
|
|
270
|
+
"function postCheck(bytes calldata hookData) external",
|
|
271
|
+
// State readers
|
|
272
|
+
"function accountGuard(address account) external view returns (address)",
|
|
273
|
+
"function accountTier1(address account) external view returns (uint256)",
|
|
274
|
+
"function accountTier2(address account) external view returns (uint256)"
|
|
275
|
+
];
|
|
276
|
+
var AIR_ACCOUNT_COMPOSITE_VALIDATOR_ABI = [
|
|
277
|
+
// ERC-7579 lifecycle
|
|
278
|
+
"function onInstall(bytes calldata data) external",
|
|
279
|
+
"function onUninstall(bytes calldata data) external",
|
|
280
|
+
"function isInitialized(address smartAccount) external view returns (bool)",
|
|
281
|
+
// ERC-7579 Validator interface
|
|
282
|
+
"function validateUserOp((address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature) userOp, bytes32 userOpHash) external returns (uint256 validationData)",
|
|
283
|
+
"function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata data) external view returns (bytes4 magicValue)"
|
|
284
|
+
];
|
|
285
|
+
var FORCE_EXIT_MODULE_ABI = [
|
|
286
|
+
// ERC-7579 lifecycle
|
|
287
|
+
"function onInstall(bytes calldata data) external",
|
|
288
|
+
"function onUninstall(bytes calldata data) external",
|
|
289
|
+
"function isInitialized(address smartAccount) external view returns (bool)",
|
|
290
|
+
// Force exit flow
|
|
291
|
+
"function proposeForceExit(address target, uint256 value, bytes calldata data) external",
|
|
292
|
+
"function approveForceExit(address account, bytes calldata guardianSig) external",
|
|
293
|
+
"function executeForceExit(address account) external",
|
|
294
|
+
"function cancelForceExit(address account) external",
|
|
295
|
+
// State readers
|
|
296
|
+
"function accountL2Type(address account) external view returns (uint8)",
|
|
297
|
+
"function getPendingExit(address account) external view returns (address target, uint256 value, bytes memory data, uint256 proposedAt, uint256 approvalBitmap, address[3] memory guardians)",
|
|
298
|
+
// Events
|
|
299
|
+
"event ExitProposed(address indexed account, address indexed target, uint256 value)",
|
|
300
|
+
"event ExitApproved(address indexed account, address indexed guardian, uint256 bitmap)",
|
|
301
|
+
"event ExitExecuted(address indexed account, address indexed target, uint256 value)",
|
|
302
|
+
"event ExitCancelled(address indexed account)"
|
|
303
|
+
];
|
|
304
|
+
var MODULE_TYPE = {
|
|
305
|
+
VALIDATOR: 1,
|
|
306
|
+
EXECUTOR: 2,
|
|
307
|
+
FALLBACK: 3,
|
|
308
|
+
HOOK: 4
|
|
309
|
+
};
|
|
310
|
+
var ALG_ID = {
|
|
311
|
+
BLS: 1,
|
|
312
|
+
ECDSA: 2,
|
|
313
|
+
P256: 3,
|
|
314
|
+
CUMULATIVE_T2: 4,
|
|
315
|
+
// P256 + BLS
|
|
316
|
+
CUMULATIVE_T3: 5,
|
|
317
|
+
// P256 + BLS + Guardian ECDSA
|
|
318
|
+
COMBINED_T1: 6,
|
|
319
|
+
// P256 AND ECDSA simultaneously
|
|
320
|
+
WEIGHTED: 7,
|
|
321
|
+
// Weighted multi-signature
|
|
322
|
+
SESSION_KEY: 8,
|
|
323
|
+
// Time-limited session key (SessionKeyValidator)
|
|
324
|
+
AGENT_SESSION_KEY: 9
|
|
325
|
+
// AI agent session key (AgentSessionKeyValidator)
|
|
326
|
+
};
|
|
327
|
+
var SESSION_KEY_VALIDATOR_ABI = [
|
|
328
|
+
// Session struct (8 fields) — authoritative tuple shape from
|
|
329
|
+
// airaccount-contract/src/validators/SessionKeyValidator.sol and
|
|
330
|
+
// packages/core/src/abis/SessionKeyValidator.json. The grant/build/read
|
|
331
|
+
// functions take/return this tuple as a single arg — NOT flat params.
|
|
332
|
+
// Canonical tuple type: (uint48,address,bytes4,bool,uint16,uint32,address[],bytes4[])
|
|
333
|
+
// ECDSA session key
|
|
334
|
+
"function grantSession(address account, address sessionKey, (uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist) cfg, bytes calldata ownerSig) external",
|
|
335
|
+
"function grantSessionDirect(address account, address sessionKey, (uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist) cfg) external",
|
|
336
|
+
"function revokeSession(address account, address sessionKey) external",
|
|
337
|
+
"function isSessionActive(address account, address sessionKey) external view returns (bool)",
|
|
338
|
+
"function getSession(address account, address sessionKey) external view returns ((uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist))",
|
|
339
|
+
"function buildGrantHash(address account, address sessionKey, (uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist) cfg) external view returns (bytes32)",
|
|
340
|
+
// P256 session key
|
|
341
|
+
"function grantP256Session(address account, bytes32 p256KeyX, bytes32 p256KeyY, (uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist) cfg, bytes calldata ownerSig) external",
|
|
342
|
+
"function grantP256SessionDirect(address account, bytes32 p256KeyX, bytes32 p256KeyY, (uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist) cfg) external",
|
|
343
|
+
"function revokeP256Session(address account, bytes32 p256KeyX, bytes32 p256KeyY) external",
|
|
344
|
+
"function isP256SessionActive(address account, bytes32 p256KeyX, bytes32 p256KeyY) external view returns (bool)",
|
|
345
|
+
"function getP256Session(address account, bytes32 p256KeyHash) external view returns ((uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist))",
|
|
346
|
+
"function buildP256GrantHash(address account, bytes32 p256KeyX, bytes32 p256KeyY, (uint48 expiry, address contractScope, bytes4 selectorScope, bool revoked, uint16 velocityLimit, uint32 velocityWindow, address[] callTargets, bytes4[] selectorAllowlist) cfg) external view returns (bytes32)",
|
|
347
|
+
// Events
|
|
348
|
+
"event SessionGranted(address indexed account, address indexed sessionKey, uint48 expiry, address contractScope, bytes4 selectorScope)",
|
|
349
|
+
"event SessionRevoked(address indexed account, address indexed sessionKey)",
|
|
350
|
+
"event P256SessionGranted(address indexed account, bytes32 indexed p256KeyHash, uint48 expiry)",
|
|
351
|
+
"event P256SessionRevoked(address indexed account, bytes32 indexed p256KeyHash)"
|
|
352
|
+
];
|
|
353
|
+
var CALLDATA_PARSER_REGISTRY_ABI = [
|
|
354
|
+
"function registerParser(address dest, address parser) external",
|
|
355
|
+
"function getParser(address dest) external view returns (address)",
|
|
356
|
+
"function transferOwnership(address newOwner) external",
|
|
357
|
+
"function parserFor(address dest) external view returns (address)",
|
|
358
|
+
"event ParserRegistered(address indexed dest, address indexed parser)",
|
|
359
|
+
"event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)"
|
|
360
|
+
];
|
|
361
|
+
var AIR_ACCOUNT_DELEGATE_ABI = [
|
|
362
|
+
// EIP-7702 初始化(仅限 EOA 自身调用)
|
|
363
|
+
"function initialize(address guardian1, bytes calldata g1Sig, address guardian2, bytes calldata g2Sig, uint256 dailyLimit) external",
|
|
364
|
+
// ERC-4337 执行
|
|
365
|
+
"function validateUserOp((address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature) userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256)",
|
|
366
|
+
"function execute(address dest, uint256 value, bytes calldata data) external",
|
|
367
|
+
"function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata data) external",
|
|
368
|
+
// 社会恢复(Rescue,EIP-7702 术语避免与 AirAccount Recovery 混淆)
|
|
369
|
+
"function initiateRescue(address rescueTo) external",
|
|
370
|
+
"function approveRescue() external",
|
|
371
|
+
"function executeRescue() external",
|
|
372
|
+
// Events
|
|
373
|
+
"event DelegateInitialized(address indexed eoa, address guard, address g1, address g2)",
|
|
374
|
+
"event RescueInitiated(address indexed eoa, address rescueTo, address indexed initiator)",
|
|
375
|
+
"event RescueApproved(address indexed eoa, address indexed guardian, uint8 approvals)",
|
|
376
|
+
"event RescueExecuted(address indexed eoa, address rescueTo, uint256 ethAmount)",
|
|
377
|
+
"event RescueCancelled(address indexed eoa)"
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
// ../airaccount/src/server/config.ts
|
|
381
|
+
function sepoliaV07Config(version = "M7") {
|
|
382
|
+
const factoryAddress = version === "M5" ? AIRACCOUNT_ADDRESSES.sepolia.factoryM5 : version === "M7r6" ? AIRACCOUNT_ADDRESSES.sepolia.factoryM7r6 : AIRACCOUNT_ADDRESSES.sepolia.factory;
|
|
383
|
+
return {
|
|
384
|
+
entryPointAddress: ENTRYPOINT_ADDRESSES["0.7" /* V0_7 */].sepolia,
|
|
385
|
+
factoryAddress,
|
|
386
|
+
validatorAddress: AIRACCOUNT_ADDRESSES.sepolia.validatorRouter
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function validateConfig(config) {
|
|
390
|
+
if (!config.rpcUrl) {
|
|
391
|
+
throw new Error("ServerConfig: rpcUrl is required");
|
|
392
|
+
}
|
|
393
|
+
if (!config.bundlerRpcUrl) {
|
|
394
|
+
throw new Error("ServerConfig: bundlerRpcUrl is required");
|
|
395
|
+
}
|
|
396
|
+
if (!config.chainId) {
|
|
397
|
+
throw new Error("ServerConfig: chainId is required");
|
|
398
|
+
}
|
|
399
|
+
const { entryPoints } = config;
|
|
400
|
+
if (!entryPoints || !entryPoints.v06 && !entryPoints.v07 && !entryPoints.v08) {
|
|
401
|
+
throw new Error("ServerConfig: at least one entryPoint version must be configured");
|
|
402
|
+
}
|
|
403
|
+
for (const [key, ep] of Object.entries(entryPoints)) {
|
|
404
|
+
if (ep) {
|
|
405
|
+
if (!ep.entryPointAddress) {
|
|
406
|
+
throw new Error(`ServerConfig: entryPoints.${key}.entryPointAddress is required`);
|
|
407
|
+
}
|
|
408
|
+
if (!ep.factoryAddress) {
|
|
409
|
+
throw new Error(`ServerConfig: entryPoints.${key}.factoryAddress is required`);
|
|
410
|
+
}
|
|
411
|
+
if (!ep.validatorAddress) {
|
|
412
|
+
throw new Error(`ServerConfig: entryPoints.${key}.validatorAddress is required`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!config.storage) {
|
|
417
|
+
throw new Error("ServerConfig: storage adapter is required");
|
|
418
|
+
}
|
|
419
|
+
if (!config.signer) {
|
|
420
|
+
throw new Error("ServerConfig: signer adapter is required");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ../airaccount/src/server/interfaces/logger.ts
|
|
425
|
+
var ConsoleLogger = class {
|
|
426
|
+
constructor(prefix = "[YAAA]") {
|
|
427
|
+
this.prefix = prefix;
|
|
428
|
+
}
|
|
429
|
+
debug(message, ...args) {
|
|
430
|
+
console.debug(`${this.prefix} ${message}`, ...args);
|
|
431
|
+
}
|
|
432
|
+
log(message, ...args) {
|
|
433
|
+
console.log(`${this.prefix} ${message}`, ...args);
|
|
434
|
+
}
|
|
435
|
+
warn(message, ...args) {
|
|
436
|
+
console.warn(`${this.prefix} ${message}`, ...args);
|
|
437
|
+
}
|
|
438
|
+
error(message, ...args) {
|
|
439
|
+
console.error(`${this.prefix} ${message}`, ...args);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
var SilentLogger = class {
|
|
443
|
+
debug() {
|
|
444
|
+
}
|
|
445
|
+
log() {
|
|
446
|
+
}
|
|
447
|
+
warn() {
|
|
448
|
+
}
|
|
449
|
+
error() {
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
var EthereumProvider = class {
|
|
453
|
+
/** Main-network read client. Pass to viem getContract / readContract calls. */
|
|
454
|
+
provider;
|
|
455
|
+
/** Bundler client — used only for raw eth_ / pimlico_ userOp JSON-RPC. */
|
|
456
|
+
bundlerProvider;
|
|
457
|
+
config;
|
|
458
|
+
logger;
|
|
459
|
+
constructor(config) {
|
|
460
|
+
this.config = config;
|
|
461
|
+
this.logger = config.logger ?? new ConsoleLogger("[EthereumProvider]");
|
|
462
|
+
this.provider = viem.createPublicClient({ transport: viem.http(config.rpcUrl) });
|
|
463
|
+
this.bundlerProvider = viem.createPublicClient({ transport: viem.http(config.bundlerRpcUrl) });
|
|
464
|
+
}
|
|
465
|
+
/** Returns the viem PublicClient for the main network RPC. */
|
|
466
|
+
getProvider() {
|
|
467
|
+
return this.provider;
|
|
468
|
+
}
|
|
469
|
+
/** Returns the viem PublicClient bound to the bundler RPC (raw .request only). */
|
|
470
|
+
getBundlerProvider() {
|
|
471
|
+
return this.bundlerProvider;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Raw bundler JSON-RPC call. The bundler exposes non-standard methods
|
|
475
|
+
* (eth_sendUserOperation, pimlico_getUserOperationGasPrice, ...) that are not in
|
|
476
|
+
* viem's typed RPC schema, so we go through the transport's request fn untyped.
|
|
477
|
+
*/
|
|
478
|
+
async bundlerRequest(method, params) {
|
|
479
|
+
return await this.bundlerProvider.request({
|
|
480
|
+
method,
|
|
481
|
+
params
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
// ── Config helpers ──────────────────────────────────────────────
|
|
485
|
+
getVersionConfig(version) {
|
|
486
|
+
const map = {
|
|
487
|
+
["0.6" /* V0_6 */]: this.config.entryPoints.v06,
|
|
488
|
+
["0.7" /* V0_7 */]: this.config.entryPoints.v07,
|
|
489
|
+
["0.8" /* V0_8 */]: this.config.entryPoints.v08
|
|
490
|
+
};
|
|
491
|
+
const versionConfig = map[version];
|
|
492
|
+
if (!versionConfig) {
|
|
493
|
+
throw new Error(`EntryPoint version ${version} is not configured`);
|
|
494
|
+
}
|
|
495
|
+
return versionConfig;
|
|
496
|
+
}
|
|
497
|
+
getEntryPointAddress(version) {
|
|
498
|
+
return this.getVersionConfig(version).entryPointAddress;
|
|
499
|
+
}
|
|
500
|
+
getFactoryAddress(version) {
|
|
501
|
+
return this.getVersionConfig(version).factoryAddress;
|
|
502
|
+
}
|
|
503
|
+
getValidatorAddress(version) {
|
|
504
|
+
return this.getVersionConfig(version).validatorAddress;
|
|
505
|
+
}
|
|
506
|
+
getDefaultVersion() {
|
|
507
|
+
const v = this.config.defaultVersion;
|
|
508
|
+
if (v === "0.7") return "0.7" /* V0_7 */;
|
|
509
|
+
if (v === "0.8") return "0.8" /* V0_8 */;
|
|
510
|
+
return "0.6" /* V0_6 */;
|
|
511
|
+
}
|
|
512
|
+
// ── Contract factories ──────────────────────────────────────────
|
|
513
|
+
/** Build a read-only viem contract bound to the main-network PublicClient. */
|
|
514
|
+
contractAt(address, abi) {
|
|
515
|
+
return viem.getContract({
|
|
516
|
+
address,
|
|
517
|
+
abi: viem.parseAbi(abi),
|
|
518
|
+
client: this.provider
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
getFactoryContract(version = "0.6" /* V0_6 */) {
|
|
522
|
+
const address = this.getFactoryAddress(version);
|
|
523
|
+
const abi = version === "0.6" /* V0_6 */ ? FACTORY_ABI_V6 : AIRACCOUNT_FACTORY_ABI;
|
|
524
|
+
return this.contractAt(address, abi);
|
|
525
|
+
}
|
|
526
|
+
getEntryPointContract(version = "0.6" /* V0_6 */) {
|
|
527
|
+
const address = this.getEntryPointAddress(version);
|
|
528
|
+
const abi = version === "0.6" /* V0_6 */ ? ENTRYPOINT_ABI_V6 : ENTRYPOINT_ABI_V7_V8;
|
|
529
|
+
return this.contractAt(address, abi);
|
|
530
|
+
}
|
|
531
|
+
getValidatorContract(version = "0.6" /* V0_6 */) {
|
|
532
|
+
const address = this.getValidatorAddress(version);
|
|
533
|
+
return this.contractAt(address, VALIDATOR_ABI);
|
|
534
|
+
}
|
|
535
|
+
getAccountContract(address) {
|
|
536
|
+
return this.contractAt(address, AIRACCOUNT_ABI);
|
|
537
|
+
}
|
|
538
|
+
// ── M7 Module contracts ─────────────────────────────────────────
|
|
539
|
+
// M7 r4 module helpers — addresses renamed to *M7r4 suffix in beta.3 to avoid ambiguity.
|
|
540
|
+
// These methods are retained for backwards compatibility; callers should pass an explicit address.
|
|
541
|
+
getAgentSessionKeyValidatorContract(address = AIRACCOUNT_ADDRESSES.sepolia.agentSessionKeyValidatorM7r4) {
|
|
542
|
+
return this.contractAt(address, AGENT_SESSION_KEY_VALIDATOR_ABI);
|
|
543
|
+
}
|
|
544
|
+
getTierGuardHookContract(address = AIRACCOUNT_ADDRESSES.sepolia.tierGuardHookM7r4) {
|
|
545
|
+
return this.contractAt(address, TIER_GUARD_HOOK_ABI);
|
|
546
|
+
}
|
|
547
|
+
getCompositeValidatorContract(address = AIRACCOUNT_ADDRESSES.sepolia.compositeValidatorM7r4) {
|
|
548
|
+
return this.contractAt(address, AIR_ACCOUNT_COMPOSITE_VALIDATOR_ABI);
|
|
549
|
+
}
|
|
550
|
+
getForceExitModuleContract(address) {
|
|
551
|
+
return this.contractAt(address, FORCE_EXIT_MODULE_ABI);
|
|
552
|
+
}
|
|
553
|
+
// ── On-chain queries ────────────────────────────────────────────
|
|
554
|
+
async getBalance(address) {
|
|
555
|
+
const balance = await this.provider.getBalance({ address });
|
|
556
|
+
return viem.formatEther(balance);
|
|
557
|
+
}
|
|
558
|
+
async getNonce(accountAddress, key = 0, version = "0.6" /* V0_6 */) {
|
|
559
|
+
const entryPoint = this.getEntryPointContract(version);
|
|
560
|
+
return await entryPoint.read.getNonce([
|
|
561
|
+
accountAddress,
|
|
562
|
+
BigInt(key)
|
|
563
|
+
]);
|
|
564
|
+
}
|
|
565
|
+
async getUserOpHash(userOp, version = "0.6" /* V0_6 */) {
|
|
566
|
+
const entryPoint = this.getEntryPointContract(version);
|
|
567
|
+
const read = entryPoint.read;
|
|
568
|
+
if (version === "0.6" /* V0_6 */) {
|
|
569
|
+
const op = userOp;
|
|
570
|
+
const userOpArray = [
|
|
571
|
+
op.sender,
|
|
572
|
+
BigInt(op.nonce),
|
|
573
|
+
op.initCode || "0x",
|
|
574
|
+
op.callData,
|
|
575
|
+
BigInt(op.callGasLimit),
|
|
576
|
+
BigInt(op.verificationGasLimit),
|
|
577
|
+
BigInt(op.preVerificationGas),
|
|
578
|
+
BigInt(op.maxFeePerGas),
|
|
579
|
+
BigInt(op.maxPriorityFeePerGas),
|
|
580
|
+
op.paymasterAndData || "0x",
|
|
581
|
+
"0x"
|
|
582
|
+
// Always use empty signature for hash calculation
|
|
583
|
+
];
|
|
584
|
+
return await read.getUserOpHash([userOpArray]);
|
|
585
|
+
} else {
|
|
586
|
+
const packedOp = userOp;
|
|
587
|
+
const packedOpArray = [
|
|
588
|
+
packedOp.sender,
|
|
589
|
+
BigInt(packedOp.nonce),
|
|
590
|
+
packedOp.initCode || "0x",
|
|
591
|
+
packedOp.callData,
|
|
592
|
+
packedOp.accountGasLimits,
|
|
593
|
+
BigInt(packedOp.preVerificationGas),
|
|
594
|
+
packedOp.gasFees,
|
|
595
|
+
packedOp.paymasterAndData || "0x",
|
|
596
|
+
"0x"
|
|
597
|
+
];
|
|
598
|
+
return await read.getUserOpHash([packedOpArray]);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// ── Bundler RPC ─────────────────────────────────────────────────
|
|
602
|
+
async estimateUserOperationGas(userOp, version = "0.6" /* V0_6 */) {
|
|
603
|
+
try {
|
|
604
|
+
return await this.bundlerRequest("eth_estimateUserOperationGas", [
|
|
605
|
+
userOp,
|
|
606
|
+
this.getEntryPointAddress(version)
|
|
607
|
+
]);
|
|
608
|
+
} catch {
|
|
609
|
+
return {
|
|
610
|
+
callGasLimit: "0x249f0",
|
|
611
|
+
verificationGasLimit: "0x3d0900",
|
|
612
|
+
// 4M — enough for M4 factory deployment + BLS verification
|
|
613
|
+
preVerificationGas: "0x11170"
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async sendUserOperation(userOp, version = "0.6" /* V0_6 */) {
|
|
618
|
+
return await this.bundlerRequest("eth_sendUserOperation", [
|
|
619
|
+
userOp,
|
|
620
|
+
this.getEntryPointAddress(version)
|
|
621
|
+
]);
|
|
622
|
+
}
|
|
623
|
+
async getUserOperationReceipt(userOpHash) {
|
|
624
|
+
return await this.bundlerRequest("eth_getUserOperationReceipt", [userOpHash]);
|
|
625
|
+
}
|
|
626
|
+
async waitForUserOp(userOpHash, maxAttempts = 60) {
|
|
627
|
+
const pollInterval = 2e3;
|
|
628
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
629
|
+
try {
|
|
630
|
+
const receipt = await this.getUserOperationReceipt(userOpHash);
|
|
631
|
+
if (receipt) {
|
|
632
|
+
const txHash = receipt.transactionHash || receipt.receipt?.transactionHash;
|
|
633
|
+
if (txHash) return txHash;
|
|
634
|
+
}
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
638
|
+
}
|
|
639
|
+
throw new Error(`UserOp timeout: ${userOpHash}`);
|
|
640
|
+
}
|
|
641
|
+
async getUserOperationGasPrice() {
|
|
642
|
+
try {
|
|
643
|
+
const gasPrice = await this.bundlerRequest("pimlico_getUserOperationGasPrice", []);
|
|
644
|
+
return {
|
|
645
|
+
maxFeePerGas: gasPrice.fast.maxFeePerGas,
|
|
646
|
+
maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas
|
|
647
|
+
};
|
|
648
|
+
} catch {
|
|
649
|
+
try {
|
|
650
|
+
const feeData = await this.provider.estimateFeesPerGas();
|
|
651
|
+
const baseFee = feeData.maxFeePerGas || viem.parseUnits("20", 9);
|
|
652
|
+
const priorityFee = feeData.maxPriorityFeePerGas || viem.parseUnits("2", 9);
|
|
653
|
+
const maxFeePerGas = baseFee * 3n / 2n;
|
|
654
|
+
const maxPriorityFeePerGas = priorityFee * 3n / 2n;
|
|
655
|
+
return {
|
|
656
|
+
maxFeePerGas: "0x" + maxFeePerGas.toString(16),
|
|
657
|
+
maxPriorityFeePerGas: "0x" + maxPriorityFeePerGas.toString(16)
|
|
658
|
+
};
|
|
659
|
+
} catch {
|
|
660
|
+
return {
|
|
661
|
+
maxFeePerGas: "0x" + viem.parseUnits("3", 9).toString(16),
|
|
662
|
+
// gwei
|
|
663
|
+
maxPriorityFeePerGas: "0x" + viem.parseUnits("1", 9).toString(16)
|
|
664
|
+
// gwei
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// ../airaccount/src/server/providers/typed-reads.ts
|
|
672
|
+
function readFn(contract, name) {
|
|
673
|
+
return contract.read[name];
|
|
674
|
+
}
|
|
675
|
+
function readValidatorGasEstimate(validator, nodeCount) {
|
|
676
|
+
return readFn(validator, "getGasEstimate")([nodeCount]);
|
|
677
|
+
}
|
|
678
|
+
function readPredictedAddress(factory, owner, salt, config) {
|
|
679
|
+
return readFn(factory, "getAddress")([owner, salt, config]);
|
|
680
|
+
}
|
|
681
|
+
function readPredictedAddressWithDefaults(factory, owner, salt, guardian1, guardian2, dailyLimit) {
|
|
682
|
+
return readFn(factory, "getAddressWithDefaults")([
|
|
683
|
+
owner,
|
|
684
|
+
salt,
|
|
685
|
+
guardian1,
|
|
686
|
+
guardian2,
|
|
687
|
+
dailyLimit
|
|
688
|
+
]);
|
|
689
|
+
}
|
|
690
|
+
async function readAccountTierLimits(account) {
|
|
691
|
+
const [tier1Limit, tier2Limit] = await Promise.all([
|
|
692
|
+
readFn(account, "tier1Limit")([]),
|
|
693
|
+
readFn(account, "tier2Limit")([])
|
|
694
|
+
]);
|
|
695
|
+
return { tier1Limit, tier2Limit };
|
|
696
|
+
}
|
|
697
|
+
function readAlgorithmApproved(account, algId) {
|
|
698
|
+
return readFn(account, "approvedAlgorithms")([algId]);
|
|
699
|
+
}
|
|
700
|
+
async function readAccountGuardAddress(account) {
|
|
701
|
+
const config = await readFn(account, "getConfigDescription")([]);
|
|
702
|
+
return config.guardAddress;
|
|
703
|
+
}
|
|
704
|
+
async function readGuardDailyAllowance(guard) {
|
|
705
|
+
const [dailyLimit, dailyRemaining] = await Promise.all([
|
|
706
|
+
readFn(guard, "dailyLimit")([]),
|
|
707
|
+
readFn(guard, "remainingDailyAllowance")([])
|
|
708
|
+
]);
|
|
709
|
+
return { dailyLimit, dailyRemaining };
|
|
710
|
+
}
|
|
711
|
+
function readBuildGrantHash(validator, account, sessionKey, cfg) {
|
|
712
|
+
return readFn(validator, "buildGrantHash")([account, sessionKey, cfg]);
|
|
713
|
+
}
|
|
714
|
+
function readBuildP256GrantHash(validator, account, keyX, keyY, cfg) {
|
|
715
|
+
return readFn(validator, "buildP256GrantHash")([account, keyX, keyY, cfg]);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ../airaccount/src/server/services/account-manager.ts
|
|
719
|
+
var ZERO32 = "0x" + "0".repeat(64);
|
|
720
|
+
var EMPTY_P256 = [ZERO32, ZERO32, ZERO32];
|
|
721
|
+
var AccountManager = class {
|
|
722
|
+
constructor(ethereum, storage, signer, logger) {
|
|
723
|
+
this.ethereum = ethereum;
|
|
724
|
+
this.storage = storage;
|
|
725
|
+
this.signer = signer;
|
|
726
|
+
this.logger = logger ?? new ConsoleLogger("[AccountManager]");
|
|
727
|
+
}
|
|
728
|
+
logger;
|
|
729
|
+
async createAccount(userId, options) {
|
|
730
|
+
const version = options?.entryPointVersion ?? this.ethereum.getDefaultVersion();
|
|
731
|
+
const versionStr = version;
|
|
732
|
+
const existingAccounts = await this.storage.getAccounts();
|
|
733
|
+
const existing = existingAccounts.find(
|
|
734
|
+
(a) => a.userId === userId && a.entryPointVersion === versionStr
|
|
735
|
+
);
|
|
736
|
+
if (existing) return existing;
|
|
737
|
+
const factory = this.ethereum.getFactoryContract(version);
|
|
738
|
+
const validatorAddress = this.ethereum.getValidatorContract(version).address || this.ethereum.getValidatorAddress(version);
|
|
739
|
+
const { address: signerAddress } = await this.signer.ensureSigner(userId);
|
|
740
|
+
const salt = options?.salt ?? Math.floor(Math.random() * 1e6);
|
|
741
|
+
const dailyLimitValue = options?.dailyLimit ?? 0n;
|
|
742
|
+
const minimalConfig = [
|
|
743
|
+
[viem.zeroAddress, viem.zeroAddress, viem.zeroAddress],
|
|
744
|
+
// guardians (address[3])
|
|
745
|
+
EMPTY_P256,
|
|
746
|
+
// guardianP256X (bytes32[3]) — v0.20.0
|
|
747
|
+
EMPTY_P256,
|
|
748
|
+
// guardianP256Y (bytes32[3]) — v0.20.0
|
|
749
|
+
dailyLimitValue,
|
|
750
|
+
// dailyLimit (0 = no guard)
|
|
751
|
+
[],
|
|
752
|
+
// approvedAlgIds
|
|
753
|
+
0n,
|
|
754
|
+
// minDailyLimit
|
|
755
|
+
[],
|
|
756
|
+
// initialTokens
|
|
757
|
+
[]
|
|
758
|
+
// initialTokenConfigs
|
|
759
|
+
];
|
|
760
|
+
const accountAddress = await readPredictedAddress(
|
|
761
|
+
factory,
|
|
762
|
+
signerAddress,
|
|
763
|
+
BigInt(salt),
|
|
764
|
+
minimalConfig
|
|
765
|
+
);
|
|
766
|
+
let deployed = false;
|
|
767
|
+
try {
|
|
768
|
+
const code = await this.ethereum.getProvider().getCode({ address: accountAddress });
|
|
769
|
+
deployed = !!code && code !== "0x";
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
const account = {
|
|
773
|
+
userId,
|
|
774
|
+
address: accountAddress,
|
|
775
|
+
signerAddress,
|
|
776
|
+
salt,
|
|
777
|
+
deployed,
|
|
778
|
+
deploymentTxHash: null,
|
|
779
|
+
validatorAddress,
|
|
780
|
+
entryPointVersion: versionStr,
|
|
781
|
+
factoryAddress: factory.address || this.ethereum.getFactoryAddress(version),
|
|
782
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
783
|
+
// Persist dailyLimit so buildUserOperation can reconstruct identical initCode at deploy time.
|
|
784
|
+
...dailyLimitValue > 0n ? { dailyLimit: dailyLimitValue.toString() } : {}
|
|
785
|
+
};
|
|
786
|
+
await this.storage.saveAccount(account);
|
|
787
|
+
return account;
|
|
788
|
+
}
|
|
789
|
+
async getAccount(userId) {
|
|
790
|
+
const account = await this.storage.findAccountByUserId(userId);
|
|
791
|
+
if (!account) return null;
|
|
792
|
+
let balance = "0";
|
|
793
|
+
try {
|
|
794
|
+
balance = await this.ethereum.getBalance(account.address);
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
const version = account.entryPointVersion || "0.6";
|
|
798
|
+
const nonce = await this.ethereum.getNonce(account.address, 0, version);
|
|
799
|
+
return { ...account, balance, nonce: nonce.toString() };
|
|
800
|
+
}
|
|
801
|
+
async getAccountAddress(userId) {
|
|
802
|
+
const account = await this.storage.findAccountByUserId(userId);
|
|
803
|
+
if (!account) throw new Error("Account not found");
|
|
804
|
+
return account.address;
|
|
805
|
+
}
|
|
806
|
+
async getAccountBalance(userId) {
|
|
807
|
+
const account = await this.storage.findAccountByUserId(userId);
|
|
808
|
+
if (!account) throw new Error("Account not found");
|
|
809
|
+
const balance = await this.ethereum.getBalance(account.address);
|
|
810
|
+
return {
|
|
811
|
+
address: account.address,
|
|
812
|
+
balance,
|
|
813
|
+
balanceInWei: viem.parseEther(balance).toString()
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
async getAccountNonce(userId) {
|
|
817
|
+
const account = await this.storage.findAccountByUserId(userId);
|
|
818
|
+
if (!account) throw new Error("Account not found");
|
|
819
|
+
const nonce = await this.ethereum.getNonce(account.address);
|
|
820
|
+
return { address: account.address, nonce: nonce.toString() };
|
|
821
|
+
}
|
|
822
|
+
async getAccountByUserId(userId) {
|
|
823
|
+
return this.storage.findAccountByUserId(userId);
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Build the acceptance hash that guardian devices must sign before account creation.
|
|
827
|
+
*
|
|
828
|
+
* Encoding: keccak256(solidityPacked(
|
|
829
|
+
* ["string","uint256","address","address","uint256","uint256"],
|
|
830
|
+
* ["ACCEPT_GUARDIAN", chainId, factoryAddress, owner, salt, dailyLimit]
|
|
831
|
+
* ))
|
|
832
|
+
*
|
|
833
|
+
* dailyLimit is bound in the hash (PR #47 / C-3) to prevent a front-runner from
|
|
834
|
+
* replaying guardian sigs with a weaker limit on the same counterfactual address.
|
|
835
|
+
*
|
|
836
|
+
* Returns the RAW keccak256 hash (no EIP-191 prefix).
|
|
837
|
+
* Guardians MUST sign via personal_sign / ethers.signMessage(ethers.getBytes(hash)).
|
|
838
|
+
* Do NOT use eth_sign — the EIP-191 "\x19Ethereum Signed Message:\n32" prefix
|
|
839
|
+
* is applied inside the contract (toEthSignedMessageHash) before ecrecover, not here.
|
|
840
|
+
*
|
|
841
|
+
* @returns raw hex keccak256 hash — encode this into the QR code shown to guardian devices
|
|
842
|
+
*/
|
|
843
|
+
buildGuardianAcceptanceHash(owner, salt, factoryAddress, chainId, dailyLimit) {
|
|
844
|
+
if (typeof salt === "number" && !Number.isSafeInteger(salt)) {
|
|
845
|
+
throw new Error(
|
|
846
|
+
`salt value ${salt} exceeds Number.MAX_SAFE_INTEGER; pass as bigint to avoid precision loss`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
return chunkXQROKLZI_cjs.keccak256(
|
|
850
|
+
chunkXQROKLZI_cjs.solidityPacked(
|
|
851
|
+
["string", "uint256", "address", "address", "uint256", "uint256"],
|
|
852
|
+
["ACCEPT_GUARDIAN", BigInt(chainId), factoryAddress, owner, BigInt(salt), dailyLimit]
|
|
853
|
+
)
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Encode calldata for modifyTierLimitsWithGuardians() — guardian-gated tier-limit change (PR #43).
|
|
858
|
+
*
|
|
859
|
+
* Both tier1 and tier2 can be raised or lowered, subject to guardian approval.
|
|
860
|
+
* Caller is responsible for building and submitting the resulting UserOp.
|
|
861
|
+
*
|
|
862
|
+
* @param tier1 New Tier-1 ceiling in wei (ECDSA-only spending; 0 = no limit)
|
|
863
|
+
* @param tier2 New Tier-2 ceiling in wei (dual-factor; 0 = no limit)
|
|
864
|
+
* @param deadline Unix timestamp — guardian sigs rejected after this
|
|
865
|
+
* @param guardianSigs 65-byte EIP-191 hex signatures from required guardians
|
|
866
|
+
*/
|
|
867
|
+
encodeModifyTierLimits(tier1, tier2, deadline, guardianSigs) {
|
|
868
|
+
return viem.encodeFunctionData({
|
|
869
|
+
abi: viem.parseAbi(AIRACCOUNT_ABI),
|
|
870
|
+
functionName: "modifyTierLimitsWithGuardians",
|
|
871
|
+
args: [tier1, tier2, deadline, guardianSigs]
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Create an AirAccount with 3 on-chain guardians:
|
|
876
|
+
* - guardian1 and guardian2: user's own devices (passkeys on phone 1 and phone 2)
|
|
877
|
+
* - guardian3: team Safe multisig (defaultCommunityGuardian, set in factory at deploy time)
|
|
878
|
+
*
|
|
879
|
+
* Both guardian1 and guardian2 must sign the acceptance hash produced by
|
|
880
|
+
* buildGuardianAcceptanceHash() before this method is called.
|
|
881
|
+
*
|
|
882
|
+
* Recovery: any 2-of-3 guardians can initiate social recovery after a 48h timelock.
|
|
883
|
+
*/
|
|
884
|
+
async createAccountWithGuardians(userId, params) {
|
|
885
|
+
if (params.guardian1.toLowerCase() === params.guardian2.toLowerCase()) {
|
|
886
|
+
throw new Error("guardian1 and guardian2 must be different addresses");
|
|
887
|
+
}
|
|
888
|
+
if (params.dailyLimit <= 0n) {
|
|
889
|
+
throw new Error("Guardian accounts require dailyLimit > 0 (on-chain enforcement)");
|
|
890
|
+
}
|
|
891
|
+
const version = params.entryPointVersion ?? this.ethereum.getDefaultVersion();
|
|
892
|
+
if (version === "0.6" /* V0_6 */) {
|
|
893
|
+
throw new Error(
|
|
894
|
+
"createAccountWithGuardians requires EntryPoint v0.7 or v0.8; v0.6 factory does not support getAddressWithDefaults"
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
const versionStr = version;
|
|
898
|
+
const existingAccounts = await this.storage.getAccounts();
|
|
899
|
+
const existing = existingAccounts.find(
|
|
900
|
+
(a) => a.userId === userId && a.entryPointVersion === versionStr && a.guardian1
|
|
901
|
+
);
|
|
902
|
+
if (existing) return existing;
|
|
903
|
+
const { address: signerAddress } = await this.signer.ensureSigner(userId);
|
|
904
|
+
const salt = params.salt ?? Math.floor(Math.random() * 1e6);
|
|
905
|
+
const factory = this.ethereum.getFactoryContract(version);
|
|
906
|
+
const factoryAddress = factory.address ?? this.ethereum.getFactoryAddress(version);
|
|
907
|
+
const accountAddress = await readPredictedAddressWithDefaults(
|
|
908
|
+
factory,
|
|
909
|
+
signerAddress,
|
|
910
|
+
BigInt(salt),
|
|
911
|
+
params.guardian1,
|
|
912
|
+
params.guardian2,
|
|
913
|
+
params.dailyLimit
|
|
914
|
+
);
|
|
915
|
+
let deployed = false;
|
|
916
|
+
try {
|
|
917
|
+
const code = await this.ethereum.getProvider().getCode({ address: accountAddress });
|
|
918
|
+
deployed = !!code && code !== "0x";
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
const validatorAddress = this.ethereum.getValidatorAddress(version);
|
|
922
|
+
const account = {
|
|
923
|
+
userId,
|
|
924
|
+
address: accountAddress,
|
|
925
|
+
signerAddress,
|
|
926
|
+
salt,
|
|
927
|
+
deployed,
|
|
928
|
+
deploymentTxHash: null,
|
|
929
|
+
validatorAddress,
|
|
930
|
+
entryPointVersion: versionStr,
|
|
931
|
+
factoryAddress,
|
|
932
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
933
|
+
// Persist dailyLimit so transfer-manager can reconstruct identical initCode at deploy time.
|
|
934
|
+
...params.dailyLimit > 0n ? { dailyLimit: params.dailyLimit.toString() } : {},
|
|
935
|
+
// Persist guardian addresses and sigs so transfer-manager can use createAccountWithDefaults
|
|
936
|
+
// to reconstruct the correct initCode on first UserOp deployment.
|
|
937
|
+
guardian1: params.guardian1,
|
|
938
|
+
guardian1Sig: params.guardian1Sig,
|
|
939
|
+
guardian2: params.guardian2,
|
|
940
|
+
guardian2Sig: params.guardian2Sig
|
|
941
|
+
};
|
|
942
|
+
await this.storage.saveAccount(account);
|
|
943
|
+
this.logger.log(`[AccountManager] account created with guardians: ${accountAddress}`);
|
|
944
|
+
return account;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
var EXECUTE_USER_OP_SELECTOR = chunkXQROKLZI_cjs.selectorFromId(
|
|
948
|
+
"executeUserOp((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes),bytes32)"
|
|
949
|
+
);
|
|
950
|
+
var EXECUTE_SELECTOR = chunkXQROKLZI_cjs.selectorFromId("execute(address,uint256,bytes)");
|
|
951
|
+
var EXECUTE_BATCH_SELECTOR = chunkXQROKLZI_cjs.selectorFromId(
|
|
952
|
+
"executeBatch(address[],uint256[],bytes[])"
|
|
953
|
+
);
|
|
954
|
+
function wrapExecuteUserOp(innerCallData) {
|
|
955
|
+
if (!/^0x[0-9a-fA-F]*$/.test(innerCallData) || innerCallData.length < 10) {
|
|
956
|
+
throw new Error("wrapExecuteUserOp: innerCallData must be 0x-prefixed calldata with a 4-byte selector");
|
|
957
|
+
}
|
|
958
|
+
const sel = innerCallData.slice(0, 10).toLowerCase();
|
|
959
|
+
if (sel !== EXECUTE_SELECTOR && sel !== EXECUTE_BATCH_SELECTOR) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
`wrapExecuteUserOp: only execute()/executeBatch() may be wrapped (got selector ${sel}); the account reverts UnsupportedInnerSelector otherwise`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
return viem.concat([EXECUTE_USER_OP_SELECTOR, innerCallData]);
|
|
965
|
+
}
|
|
966
|
+
function isExecuteUserOpWrapped(callData) {
|
|
967
|
+
return callData.slice(0, 10).toLowerCase() === EXECUTE_USER_OP_SELECTOR;
|
|
968
|
+
}
|
|
969
|
+
var PaymasterPriceStalenessError = class extends Error {
|
|
970
|
+
constructor(paymasterAddress, ageSeconds, thresholdSeconds) {
|
|
971
|
+
super(
|
|
972
|
+
`Paymaster ${paymasterAddress} price is stale (age: ${Math.floor(ageSeconds / 60)}min, threshold: ${Math.floor(thresholdSeconds / 60)}min). Call updatePrice() on the paymaster contract before retrying.`
|
|
973
|
+
);
|
|
974
|
+
this.paymasterAddress = paymasterAddress;
|
|
975
|
+
this.ageSeconds = ageSeconds;
|
|
976
|
+
this.thresholdSeconds = thresholdSeconds;
|
|
977
|
+
this.name = "PaymasterPriceStalenessError";
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
var PAYMASTER_PRICE_ABI = viem.parseAbi([
|
|
981
|
+
"function token() view returns (address)",
|
|
982
|
+
"function cachedPriceTimestamp() view returns (uint256)",
|
|
983
|
+
"function priceStalenessThreshold() view returns (uint256)",
|
|
984
|
+
"function updatePrice() external"
|
|
985
|
+
]);
|
|
986
|
+
var SUPER_PAYMASTER_DETECT_ABI = viem.parseAbi([
|
|
987
|
+
"function owner() view returns (address)",
|
|
988
|
+
"function operators(address) view returns (bool,uint256,address,uint256)"
|
|
989
|
+
]);
|
|
990
|
+
var PaymasterManager = class {
|
|
991
|
+
constructor(ethereum, storage, logger) {
|
|
992
|
+
this.ethereum = ethereum;
|
|
993
|
+
this.storage = storage;
|
|
994
|
+
this.logger = logger ?? new ConsoleLogger("[PaymasterManager]");
|
|
995
|
+
}
|
|
996
|
+
logger;
|
|
997
|
+
async getAvailablePaymasters(userId) {
|
|
998
|
+
const paymasters = await this.storage.getPaymasters(userId);
|
|
999
|
+
return paymasters.map((config) => ({
|
|
1000
|
+
name: config.name,
|
|
1001
|
+
address: config.address,
|
|
1002
|
+
configured: !!config.address && config.address !== "0x"
|
|
1003
|
+
}));
|
|
1004
|
+
}
|
|
1005
|
+
async addCustomPaymaster(userId, name, address, type = "custom", apiKey, endpoint) {
|
|
1006
|
+
const paymaster = {
|
|
1007
|
+
id: `${userId}-${name}-${Date.now()}`,
|
|
1008
|
+
name,
|
|
1009
|
+
address,
|
|
1010
|
+
type,
|
|
1011
|
+
apiKey,
|
|
1012
|
+
endpoint,
|
|
1013
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1014
|
+
};
|
|
1015
|
+
await this.storage.savePaymaster(userId, paymaster);
|
|
1016
|
+
}
|
|
1017
|
+
async removeCustomPaymaster(userId, name) {
|
|
1018
|
+
return this.storage.removePaymaster(userId, name);
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Check whether a paymaster's on-chain price cache is still fresh.
|
|
1022
|
+
* Returns `{ fresh, ageSeconds, thresholdSeconds }`.
|
|
1023
|
+
* Throws if the contract does not implement `cachedPriceTimestamp()` / `priceStalenessThreshold()`.
|
|
1024
|
+
*/
|
|
1025
|
+
async checkPriceFreshness(paymasterAddress) {
|
|
1026
|
+
const provider = this.ethereum.getProvider();
|
|
1027
|
+
const contract = viem.getContract({
|
|
1028
|
+
address: paymasterAddress,
|
|
1029
|
+
abi: PAYMASTER_PRICE_ABI,
|
|
1030
|
+
client: provider
|
|
1031
|
+
});
|
|
1032
|
+
const [timestamp, threshold] = await Promise.all([
|
|
1033
|
+
contract.read.cachedPriceTimestamp(),
|
|
1034
|
+
contract.read.priceStalenessThreshold()
|
|
1035
|
+
]);
|
|
1036
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
1037
|
+
const ageSeconds = nowSeconds - Number(timestamp);
|
|
1038
|
+
const thresholdSeconds = Number(threshold);
|
|
1039
|
+
return {
|
|
1040
|
+
fresh: ageSeconds <= thresholdSeconds,
|
|
1041
|
+
ageSeconds,
|
|
1042
|
+
thresholdSeconds
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Call `updatePrice()` on a paymaster contract (permissionless).
|
|
1047
|
+
* Useful when `checkPriceFreshness()` reports stale price.
|
|
1048
|
+
*
|
|
1049
|
+
* @param walletClient - A viem WalletClient (with an account) that will send
|
|
1050
|
+
* the transaction (must have gas). Replaces the former ethers Signer param.
|
|
1051
|
+
*/
|
|
1052
|
+
async updatePrice(paymasterAddress, walletClient) {
|
|
1053
|
+
const account = walletClient.account;
|
|
1054
|
+
if (!account) {
|
|
1055
|
+
throw new Error("updatePrice requires a WalletClient with a configured account");
|
|
1056
|
+
}
|
|
1057
|
+
const hash = await walletClient.writeContract({
|
|
1058
|
+
address: paymasterAddress,
|
|
1059
|
+
abi: PAYMASTER_PRICE_ABI,
|
|
1060
|
+
functionName: "updatePrice",
|
|
1061
|
+
args: [],
|
|
1062
|
+
gas: BigInt(3e5),
|
|
1063
|
+
account,
|
|
1064
|
+
chain: walletClient.chain
|
|
1065
|
+
});
|
|
1066
|
+
await this.ethereum.getProvider().waitForTransactionReceipt({ hash });
|
|
1067
|
+
this.logger.log(`Paymaster ${paymasterAddress} price updated, tx: ${hash}`);
|
|
1068
|
+
return hash;
|
|
1069
|
+
}
|
|
1070
|
+
async getPaymasterData(userId, paymasterName, userOp, entryPoint, customAddress, options) {
|
|
1071
|
+
if (paymasterName === "custom-user-provided" && customAddress) {
|
|
1072
|
+
const formattedAddress = customAddress.toLowerCase().startsWith("0x") ? customAddress : `0x${customAddress}`;
|
|
1073
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(formattedAddress)) {
|
|
1074
|
+
throw new Error(`Invalid paymaster address format: ${customAddress}`);
|
|
1075
|
+
}
|
|
1076
|
+
const isV07OrV08 = entryPoint.toLowerCase() === "0x0000000071727De22E5E9d8BAf0edAc6f37da032".toLowerCase() || entryPoint.toLowerCase() === "0x0576a174D229E3cFA37253523E645A78A0C91B57".toLowerCase();
|
|
1077
|
+
if (isV07OrV08) {
|
|
1078
|
+
const provider = this.ethereum.getProvider();
|
|
1079
|
+
let isSuperPaymaster = false;
|
|
1080
|
+
let operatorAddress = "0x";
|
|
1081
|
+
try {
|
|
1082
|
+
const spContract = viem.getContract({
|
|
1083
|
+
address: formattedAddress,
|
|
1084
|
+
abi: SUPER_PAYMASTER_DETECT_ABI,
|
|
1085
|
+
client: provider
|
|
1086
|
+
});
|
|
1087
|
+
const owner = await spContract.read.owner();
|
|
1088
|
+
const opInfo = await spContract.read.operators([owner]);
|
|
1089
|
+
if (opInfo && opInfo[0] === true) {
|
|
1090
|
+
isSuperPaymaster = true;
|
|
1091
|
+
operatorAddress = owner;
|
|
1092
|
+
this.logger.log(`SuperPaymaster detected, operator: ${operatorAddress}`);
|
|
1093
|
+
}
|
|
1094
|
+
} catch {
|
|
1095
|
+
}
|
|
1096
|
+
if (isSuperPaymaster) {
|
|
1097
|
+
const verGas = BigInt(8e4);
|
|
1098
|
+
const postGas = BigInt(3e5);
|
|
1099
|
+
const maxRate = (BigInt(1) << BigInt(256)) - BigInt(1);
|
|
1100
|
+
return viem.concat([
|
|
1101
|
+
formattedAddress,
|
|
1102
|
+
viem.numberToHex(verGas, { size: 16 }),
|
|
1103
|
+
viem.numberToHex(postGas, { size: 16 }),
|
|
1104
|
+
operatorAddress,
|
|
1105
|
+
viem.numberToHex(maxRate, { size: 32 })
|
|
1106
|
+
]);
|
|
1107
|
+
}
|
|
1108
|
+
const paymasterVerificationGasLimit = BigInt(196608);
|
|
1109
|
+
const paymasterPostOpGasLimit = BigInt(196608);
|
|
1110
|
+
let tokenAddress = options?.tokenAddress ?? null;
|
|
1111
|
+
if (tokenAddress) {
|
|
1112
|
+
this.logger.log(`PaymasterV4 token from options: ${tokenAddress}`);
|
|
1113
|
+
} else {
|
|
1114
|
+
try {
|
|
1115
|
+
const pmContract = viem.getContract({
|
|
1116
|
+
address: formattedAddress,
|
|
1117
|
+
abi: PAYMASTER_PRICE_ABI,
|
|
1118
|
+
client: provider
|
|
1119
|
+
});
|
|
1120
|
+
tokenAddress = await pmContract.read.token();
|
|
1121
|
+
if (tokenAddress === viem.zeroAddress) tokenAddress = null;
|
|
1122
|
+
if (tokenAddress) {
|
|
1123
|
+
this.logger.log(`PaymasterV4 token auto-detected: ${tokenAddress}`);
|
|
1124
|
+
}
|
|
1125
|
+
} catch {
|
|
1126
|
+
this.logger.log(`PaymasterV4 token() not available, paymasterData will have no token`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
const parts = [
|
|
1130
|
+
formattedAddress,
|
|
1131
|
+
viem.numberToHex(paymasterVerificationGasLimit, { size: 16 }),
|
|
1132
|
+
viem.numberToHex(paymasterPostOpGasLimit, { size: 16 })
|
|
1133
|
+
];
|
|
1134
|
+
if (tokenAddress) {
|
|
1135
|
+
parts.push(tokenAddress);
|
|
1136
|
+
}
|
|
1137
|
+
return viem.concat(parts);
|
|
1138
|
+
}
|
|
1139
|
+
return formattedAddress;
|
|
1140
|
+
}
|
|
1141
|
+
const paymasters = await this.storage.getPaymasters(userId);
|
|
1142
|
+
const config = paymasters.find((p) => p.name === paymasterName);
|
|
1143
|
+
if (!config) {
|
|
1144
|
+
throw new Error(`Paymaster ${paymasterName} not found`);
|
|
1145
|
+
}
|
|
1146
|
+
switch (config.type) {
|
|
1147
|
+
case "pimlico":
|
|
1148
|
+
if (!config.apiKey) return "0x";
|
|
1149
|
+
return this.getPimlicoPaymasterData(config, userOp, entryPoint);
|
|
1150
|
+
case "stackup":
|
|
1151
|
+
if (!config.apiKey) return "0x";
|
|
1152
|
+
return this.getStackUpPaymasterData(config, userOp, entryPoint);
|
|
1153
|
+
case "alchemy":
|
|
1154
|
+
if (!config.apiKey) return "0x";
|
|
1155
|
+
return this.getAlchemyPaymasterData(config, userOp, entryPoint);
|
|
1156
|
+
case "custom":
|
|
1157
|
+
if (config.address.toLowerCase() === "0x0000000000325602a77416A16136FDafd04b299f".toLowerCase() && config.apiKey) {
|
|
1158
|
+
return this.getPimlicoPaymasterData(
|
|
1159
|
+
{ ...config, type: "pimlico", endpoint: "https://api.pimlico.io/v2/11155111/rpc" },
|
|
1160
|
+
userOp,
|
|
1161
|
+
entryPoint
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
return config.address;
|
|
1165
|
+
default:
|
|
1166
|
+
return "0x";
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
async getPimlicoPaymasterData(config, userOp, entryPoint) {
|
|
1170
|
+
const url = `${config.endpoint}?apikey=${config.apiKey}`;
|
|
1171
|
+
const response = await globalThis.fetch(url, {
|
|
1172
|
+
method: "POST",
|
|
1173
|
+
headers: { "Content-Type": "application/json" },
|
|
1174
|
+
body: JSON.stringify({
|
|
1175
|
+
jsonrpc: "2.0",
|
|
1176
|
+
method: "pm_sponsorUserOperation",
|
|
1177
|
+
params: [userOp, entryPoint, {}],
|
|
1178
|
+
id: 1
|
|
1179
|
+
})
|
|
1180
|
+
});
|
|
1181
|
+
const result = await response.json();
|
|
1182
|
+
if (result.error) {
|
|
1183
|
+
throw new Error(
|
|
1184
|
+
`Pimlico sponsorship failed: ${result.error.message || JSON.stringify(result.error)}`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
if (result.result) {
|
|
1188
|
+
if (result.result.paymasterAndData) {
|
|
1189
|
+
return result.result.paymasterAndData;
|
|
1190
|
+
}
|
|
1191
|
+
if (result.result.paymaster) {
|
|
1192
|
+
return viem.concat([
|
|
1193
|
+
result.result.paymaster,
|
|
1194
|
+
viem.numberToHex(BigInt(result.result.paymasterVerificationGasLimit || "0x30000"), {
|
|
1195
|
+
size: 16
|
|
1196
|
+
}),
|
|
1197
|
+
viem.numberToHex(BigInt(result.result.paymasterPostOpGasLimit || "0x30000"), { size: 16 }),
|
|
1198
|
+
result.result.paymasterData || "0x"
|
|
1199
|
+
]);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
throw new Error("Pimlico API did not return valid paymaster data");
|
|
1203
|
+
}
|
|
1204
|
+
async getStackUpPaymasterData(config, userOp, entryPoint) {
|
|
1205
|
+
try {
|
|
1206
|
+
const response = await globalThis.fetch(`${config.endpoint}/${config.apiKey}`, {
|
|
1207
|
+
method: "POST",
|
|
1208
|
+
headers: { "Content-Type": "application/json" },
|
|
1209
|
+
body: JSON.stringify({
|
|
1210
|
+
jsonrpc: "2.0",
|
|
1211
|
+
method: "pm_sponsorUserOperation",
|
|
1212
|
+
params: { userOperation: userOp, entryPoint, context: { type: "payg" } },
|
|
1213
|
+
id: 1
|
|
1214
|
+
})
|
|
1215
|
+
});
|
|
1216
|
+
const result = await response.json();
|
|
1217
|
+
if (result.error) return "0x";
|
|
1218
|
+
return result.result || "0x";
|
|
1219
|
+
} catch {
|
|
1220
|
+
return "0x";
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async getAlchemyPaymasterData(config, userOp, entryPoint) {
|
|
1224
|
+
try {
|
|
1225
|
+
const response = await globalThis.fetch(`${config.endpoint}/${config.apiKey}`, {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
headers: { "Content-Type": "application/json" },
|
|
1228
|
+
body: JSON.stringify({
|
|
1229
|
+
jsonrpc: "2.0",
|
|
1230
|
+
method: "alchemy_requestGasAndPaymasterAndData",
|
|
1231
|
+
params: [{ policyId: "default", entryPoint, userOperation: userOp }],
|
|
1232
|
+
id: 1
|
|
1233
|
+
})
|
|
1234
|
+
});
|
|
1235
|
+
const result = await response.json();
|
|
1236
|
+
if (result.error) return "0x";
|
|
1237
|
+
return result.result?.paymasterAndData || "0x";
|
|
1238
|
+
} catch {
|
|
1239
|
+
return "0x";
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// ../airaccount/src/server/services/transfer-manager.ts
|
|
1245
|
+
var ZERO322 = "0x" + "0".repeat(64);
|
|
1246
|
+
var EMPTY_P2562 = [ZERO322, ZERO322, ZERO322];
|
|
1247
|
+
var AIRACCOUNT_ABI_PARSED = viem.parseAbi(AIRACCOUNT_ABI);
|
|
1248
|
+
var AIRACCOUNT_FACTORY_ABI_PARSED = viem.parseAbi(AIRACCOUNT_FACTORY_ABI);
|
|
1249
|
+
var FACTORY_ABI_V6_PARSED = viem.parseAbi(FACTORY_ABI_V6);
|
|
1250
|
+
var VALIDATOR_GETTER_ABI = viem.parseAbi(["function validator() view returns (address)"]);
|
|
1251
|
+
function encodeFn(abi, functionName, args) {
|
|
1252
|
+
return viem.encodeFunctionData({ abi, functionName, args });
|
|
1253
|
+
}
|
|
1254
|
+
async function detectSignatureStrategy(provider, accountAddress) {
|
|
1255
|
+
try {
|
|
1256
|
+
const accountCode = await provider.getCode({ address: accountAddress });
|
|
1257
|
+
if (!accountCode || accountCode === "0x") {
|
|
1258
|
+
return { useECDSA: true, isCompositeValidator: true };
|
|
1259
|
+
}
|
|
1260
|
+
const v = await provider.readContract({
|
|
1261
|
+
address: accountAddress,
|
|
1262
|
+
abi: VALIDATOR_GETTER_ABI,
|
|
1263
|
+
functionName: "validator"
|
|
1264
|
+
});
|
|
1265
|
+
return { useECDSA: v === viem.zeroAddress, isCompositeValidator: true };
|
|
1266
|
+
} catch {
|
|
1267
|
+
return { useECDSA: true, isCompositeValidator: false };
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function generateId() {
|
|
1271
|
+
const hex = () => Math.random().toString(16).slice(2, 10);
|
|
1272
|
+
return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`;
|
|
1273
|
+
}
|
|
1274
|
+
var TransferManager = class {
|
|
1275
|
+
constructor(ethereum, accountManager, blsService, paymasterManager, tokenService, storage, signer, logger, guardChecker) {
|
|
1276
|
+
this.ethereum = ethereum;
|
|
1277
|
+
this.accountManager = accountManager;
|
|
1278
|
+
this.blsService = blsService;
|
|
1279
|
+
this.paymasterManager = paymasterManager;
|
|
1280
|
+
this.tokenService = tokenService;
|
|
1281
|
+
this.storage = storage;
|
|
1282
|
+
this.signer = signer;
|
|
1283
|
+
this.logger = logger ?? new ConsoleLogger("[TransferManager]");
|
|
1284
|
+
this.guardChecker = guardChecker ?? null;
|
|
1285
|
+
}
|
|
1286
|
+
logger;
|
|
1287
|
+
guardChecker;
|
|
1288
|
+
async executeTransfer(userId, params) {
|
|
1289
|
+
const account = await this.accountManager.getAccountByUserId(userId);
|
|
1290
|
+
if (!account) throw new Error("User account not found");
|
|
1291
|
+
const code = await this.ethereum.getProvider().getCode({ address: account.address });
|
|
1292
|
+
const needsDeployment = !code || code === "0x";
|
|
1293
|
+
if (needsDeployment) {
|
|
1294
|
+
this.logger.log("Account needs deployment, will deploy with first transaction");
|
|
1295
|
+
}
|
|
1296
|
+
const smartAccountBalance = parseFloat(await this.ethereum.getBalance(account.address));
|
|
1297
|
+
const isTokenTransfer = !!params.tokenAddress;
|
|
1298
|
+
const transferAmount = isTokenTransfer ? 0 : parseFloat(params.amount);
|
|
1299
|
+
if (!params.usePaymaster) {
|
|
1300
|
+
const minRequiredBalance = 2e-4;
|
|
1301
|
+
const totalNeeded = transferAmount + minRequiredBalance;
|
|
1302
|
+
if (smartAccountBalance < totalNeeded) {
|
|
1303
|
+
throw new Error(
|
|
1304
|
+
`Insufficient balance: Account has ${smartAccountBalance} ETH but needs ${totalNeeded} ETH`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
} else if (!isTokenTransfer && transferAmount > smartAccountBalance) {
|
|
1308
|
+
throw new Error(
|
|
1309
|
+
`Insufficient balance: Account has ${smartAccountBalance} ETH but trying to send ${transferAmount} ETH`
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
const version = account.entryPointVersion || "0.6";
|
|
1313
|
+
const userOp = await this.buildUserOperation(
|
|
1314
|
+
userId,
|
|
1315
|
+
account.address,
|
|
1316
|
+
params.to,
|
|
1317
|
+
params.amount,
|
|
1318
|
+
params.data || "0x",
|
|
1319
|
+
params.usePaymaster,
|
|
1320
|
+
params.paymasterAddress,
|
|
1321
|
+
params.paymasterData,
|
|
1322
|
+
params.tokenAddress,
|
|
1323
|
+
version,
|
|
1324
|
+
params.paymasterTokenAddress,
|
|
1325
|
+
params.wrapExecuteUserOp ?? false
|
|
1326
|
+
);
|
|
1327
|
+
const userOpHash = await this.ethereum.getUserOpHash(userOp, version);
|
|
1328
|
+
await this.signer.ensureSigner(userId);
|
|
1329
|
+
const assertionCtx = params.passkeyAssertion ? { assertion: params.passkeyAssertion } : void 0;
|
|
1330
|
+
let useECDSA = false;
|
|
1331
|
+
let isCompositeValidator = false;
|
|
1332
|
+
if (version === "0.7" /* V0_7 */ || version === "0.8" /* V0_8 */) {
|
|
1333
|
+
const provider = this.ethereum.getProvider();
|
|
1334
|
+
({ useECDSA, isCompositeValidator } = await detectSignatureStrategy(
|
|
1335
|
+
provider,
|
|
1336
|
+
account.address
|
|
1337
|
+
));
|
|
1338
|
+
}
|
|
1339
|
+
if (useECDSA) {
|
|
1340
|
+
const ecdsaSig = await this.signer.signMessage(
|
|
1341
|
+
userId,
|
|
1342
|
+
viem.hexToBytes(userOpHash),
|
|
1343
|
+
assertionCtx
|
|
1344
|
+
);
|
|
1345
|
+
if (isCompositeValidator) {
|
|
1346
|
+
this.logger.log("ECDSA path for compositeValidator: prepending algId prefix");
|
|
1347
|
+
userOp.signature = viem.concat([
|
|
1348
|
+
viem.numberToHex(ALG_ID.ECDSA, { size: 1 }),
|
|
1349
|
+
ecdsaSig
|
|
1350
|
+
]);
|
|
1351
|
+
} else {
|
|
1352
|
+
this.logger.log("ECDSA path for non-compositeValidator: raw signature");
|
|
1353
|
+
userOp.signature = ecdsaSig;
|
|
1354
|
+
}
|
|
1355
|
+
} else if (params.useAirAccountTiering && this.guardChecker) {
|
|
1356
|
+
const transferValue = params.tokenAddress ? 0n : viem.parseEther(params.amount);
|
|
1357
|
+
const preCheck = await this.guardChecker.preCheck(account.address, transferValue);
|
|
1358
|
+
if (!preCheck.ok) {
|
|
1359
|
+
throw new Error(`Guard pre-check failed: ${preCheck.errors.join("; ")}`);
|
|
1360
|
+
}
|
|
1361
|
+
this.logger.log(
|
|
1362
|
+
`Tier ${preCheck.tier} selected (algId=0x${preCheck.algId.toString(16).padStart(2, "0")})`
|
|
1363
|
+
);
|
|
1364
|
+
userOp.signature = await this.blsService.generateTieredSignature({
|
|
1365
|
+
tier: preCheck.tier,
|
|
1366
|
+
userId,
|
|
1367
|
+
userOpHash,
|
|
1368
|
+
p256Signature: params.p256Signature,
|
|
1369
|
+
guardianSigner: params.guardianSigner,
|
|
1370
|
+
ctx: assertionCtx
|
|
1371
|
+
});
|
|
1372
|
+
} else {
|
|
1373
|
+
const blsData = await this.blsService.generateBLSSignature(userId, userOpHash, assertionCtx);
|
|
1374
|
+
const packedBls = await this.blsService.packSignature(blsData);
|
|
1375
|
+
userOp.signature = viem.concat([
|
|
1376
|
+
viem.numberToHex(ALG_ID.BLS, { size: 1 }),
|
|
1377
|
+
packedBls
|
|
1378
|
+
]);
|
|
1379
|
+
}
|
|
1380
|
+
const transferId = generateId();
|
|
1381
|
+
let tokenSymbol = "ETH";
|
|
1382
|
+
if (params.tokenAddress) {
|
|
1383
|
+
try {
|
|
1384
|
+
const tokenInfo = await this.tokenService.getTokenInfo(params.tokenAddress);
|
|
1385
|
+
tokenSymbol = tokenInfo.symbol;
|
|
1386
|
+
} catch {
|
|
1387
|
+
tokenSymbol = `${params.tokenAddress.slice(0, 6)}...${params.tokenAddress.slice(-4)}`;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
await this.storage.saveTransfer({
|
|
1391
|
+
id: transferId,
|
|
1392
|
+
userId,
|
|
1393
|
+
from: account.address,
|
|
1394
|
+
to: params.to,
|
|
1395
|
+
amount: params.amount,
|
|
1396
|
+
data: params.data,
|
|
1397
|
+
userOpHash,
|
|
1398
|
+
status: "pending",
|
|
1399
|
+
nodeIndices: [],
|
|
1400
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1401
|
+
tokenAddress: params.tokenAddress,
|
|
1402
|
+
tokenSymbol
|
|
1403
|
+
});
|
|
1404
|
+
this.processTransferAsync(transferId, userOp, account.address, version);
|
|
1405
|
+
return {
|
|
1406
|
+
success: true,
|
|
1407
|
+
transferId,
|
|
1408
|
+
userOpHash,
|
|
1409
|
+
status: "pending",
|
|
1410
|
+
message: "Transfer submitted successfully. Use transferId to check status.",
|
|
1411
|
+
from: account.address,
|
|
1412
|
+
to: params.to,
|
|
1413
|
+
amount: params.amount
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
async processTransferAsync(transferId, userOp, from, version) {
|
|
1417
|
+
try {
|
|
1418
|
+
const formatted = this.formatUserOpForBundler(userOp, version);
|
|
1419
|
+
const bundlerUserOpHash = await this.ethereum.sendUserOperation(formatted, version);
|
|
1420
|
+
await this.storage.updateTransfer(transferId, {
|
|
1421
|
+
bundlerUserOpHash,
|
|
1422
|
+
status: "submitted",
|
|
1423
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1424
|
+
});
|
|
1425
|
+
const txHash = await this.ethereum.waitForUserOp(bundlerUserOpHash);
|
|
1426
|
+
await this.storage.updateTransfer(transferId, {
|
|
1427
|
+
transactionHash: txHash,
|
|
1428
|
+
status: "completed",
|
|
1429
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1430
|
+
});
|
|
1431
|
+
const code = await this.ethereum.getProvider().getCode({ address: from });
|
|
1432
|
+
if (code && code !== "0x") {
|
|
1433
|
+
const account = (await this.storage.getAccounts()).find((a) => a.address === from);
|
|
1434
|
+
if (account && !account.deployed) {
|
|
1435
|
+
await this.storage.updateAccount(account.userId, {
|
|
1436
|
+
deployed: true,
|
|
1437
|
+
deploymentTxHash: txHash
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
1443
|
+
if (message.includes("expires too soon") || message.includes("AA32") || message.includes("paymaster deposit not locked")) {
|
|
1444
|
+
const validUntilMatch = message.match(/validUntil=(\d+)/);
|
|
1445
|
+
const hint = validUntilMatch ? ` (validUntil=${validUntilMatch[1]}, expired ${Math.floor(Date.now() / 1e3) - Number(validUntilMatch[1])}s ago)` : "";
|
|
1446
|
+
message = `Paymaster price is stale${hint}. Call paymasterManager.checkPriceFreshness(paymasterAddress) to diagnose, then paymasterManager.updatePrice(paymasterAddress, signer) to refresh. Original error: ${message}`;
|
|
1447
|
+
error = new PaymasterPriceStalenessError(
|
|
1448
|
+
"unknown",
|
|
1449
|
+
0,
|
|
1450
|
+
0
|
|
1451
|
+
);
|
|
1452
|
+
error.message = message;
|
|
1453
|
+
}
|
|
1454
|
+
await this.storage.updateTransfer(transferId, {
|
|
1455
|
+
status: "failed",
|
|
1456
|
+
error: message,
|
|
1457
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1458
|
+
});
|
|
1459
|
+
this.logger.error(`Transfer ${transferId} failed: ${message}`);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async estimateGas(userId, params) {
|
|
1463
|
+
const account = await this.accountManager.getAccountByUserId(userId);
|
|
1464
|
+
if (!account) throw new Error("User account not found");
|
|
1465
|
+
const version = account.entryPointVersion || "0.6";
|
|
1466
|
+
const userOp = await this.buildUserOperation(
|
|
1467
|
+
userId,
|
|
1468
|
+
account.address,
|
|
1469
|
+
params.to,
|
|
1470
|
+
params.amount,
|
|
1471
|
+
params.data || "0x",
|
|
1472
|
+
false,
|
|
1473
|
+
void 0,
|
|
1474
|
+
void 0,
|
|
1475
|
+
params.tokenAddress,
|
|
1476
|
+
version,
|
|
1477
|
+
void 0,
|
|
1478
|
+
params.wrapExecuteUserOp ?? false
|
|
1479
|
+
);
|
|
1480
|
+
const formatted = this.formatUserOpForBundler(userOp, version);
|
|
1481
|
+
const gasEstimates = await this.ethereum.estimateUserOperationGas(formatted, version);
|
|
1482
|
+
const gasPrices = await this.ethereum.getUserOperationGasPrice();
|
|
1483
|
+
const validatorContract = this.ethereum.getValidatorContract(version);
|
|
1484
|
+
const validatorGasEstimate = await readValidatorGasEstimate(validatorContract, 3n);
|
|
1485
|
+
return {
|
|
1486
|
+
callGasLimit: gasEstimates.callGasLimit,
|
|
1487
|
+
verificationGasLimit: gasEstimates.verificationGasLimit,
|
|
1488
|
+
preVerificationGas: gasEstimates.preVerificationGas,
|
|
1489
|
+
validatorGasEstimate: validatorGasEstimate.toString(),
|
|
1490
|
+
totalGasEstimate: (BigInt(gasEstimates.callGasLimit) + BigInt(gasEstimates.verificationGasLimit) + BigInt(gasEstimates.preVerificationGas)).toString(),
|
|
1491
|
+
maxFeePerGas: gasPrices.maxFeePerGas,
|
|
1492
|
+
maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
async getTransferStatus(userId, transferId) {
|
|
1496
|
+
const transfer = await this.storage.findTransferById(transferId);
|
|
1497
|
+
if (!transfer || transfer.userId !== userId) {
|
|
1498
|
+
throw new Error("Transfer not found");
|
|
1499
|
+
}
|
|
1500
|
+
const response = { ...transfer };
|
|
1501
|
+
if (transfer.status === "pending" || transfer.status === "submitted") {
|
|
1502
|
+
const elapsed = Math.floor((Date.now() - new Date(transfer.createdAt).getTime()) / 1e3);
|
|
1503
|
+
response.elapsedSeconds = elapsed;
|
|
1504
|
+
}
|
|
1505
|
+
if (transfer.transactionHash) {
|
|
1506
|
+
response.explorerUrl = `https://sepolia.etherscan.io/tx/${transfer.transactionHash}`;
|
|
1507
|
+
}
|
|
1508
|
+
const statusDescriptions = {
|
|
1509
|
+
pending: "Preparing transaction and generating signatures",
|
|
1510
|
+
submitted: "Transaction submitted to bundler, waiting for confirmation",
|
|
1511
|
+
completed: "Transaction confirmed on chain",
|
|
1512
|
+
failed: "Transaction failed"
|
|
1513
|
+
};
|
|
1514
|
+
response.statusDescription = statusDescriptions[transfer.status] || transfer.status;
|
|
1515
|
+
return response;
|
|
1516
|
+
}
|
|
1517
|
+
async getTransferHistory(userId, page = 1, limit = 10) {
|
|
1518
|
+
const transfers = await this.storage.findTransfersByUserId(userId);
|
|
1519
|
+
if (!transfers || transfers.length === 0) {
|
|
1520
|
+
return { transfers: [], total: 0, page, limit, totalPages: 0 };
|
|
1521
|
+
}
|
|
1522
|
+
transfers.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
1523
|
+
const start = (page - 1) * limit;
|
|
1524
|
+
const paginated = transfers.slice(start, start + limit);
|
|
1525
|
+
return {
|
|
1526
|
+
transfers: paginated,
|
|
1527
|
+
total: transfers.length,
|
|
1528
|
+
page,
|
|
1529
|
+
limit,
|
|
1530
|
+
totalPages: Math.ceil(transfers.length / limit)
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
// ── Private helpers ─────────────────────────────────────────────
|
|
1534
|
+
async buildUserOperation(userId, sender, to, amount, data, usePaymaster, paymasterAddress, _paymasterData, tokenAddress, version = "0.6" /* V0_6 */, paymasterTokenAddress, wrapExecuteUserOpFlag = false) {
|
|
1535
|
+
const nonce = await this.ethereum.getNonce(sender, 0, version);
|
|
1536
|
+
const provider = this.ethereum.getProvider();
|
|
1537
|
+
const code = await provider.getCode({ address: sender });
|
|
1538
|
+
const needsDeployment = !code || code === "0x";
|
|
1539
|
+
let initCode = "0x";
|
|
1540
|
+
if (needsDeployment) {
|
|
1541
|
+
const accounts = await this.storage.getAccounts();
|
|
1542
|
+
const account = accounts.find((a) => a.address === sender);
|
|
1543
|
+
if (account) {
|
|
1544
|
+
const factory = this.ethereum.getFactoryContract(version);
|
|
1545
|
+
const factoryAddress = factory.address;
|
|
1546
|
+
let deployCalldata;
|
|
1547
|
+
if (version === "0.7" /* V0_7 */ || version === "0.8" /* V0_8 */) {
|
|
1548
|
+
const storedDailyLimit = account.dailyLimit ? BigInt(account.dailyLimit) : 0n;
|
|
1549
|
+
if (account.guardian1 && account.guardian2 && account.guardian1Sig && account.guardian2Sig) {
|
|
1550
|
+
const sig1 = account.guardian1Sig.startsWith("0x") ? account.guardian1Sig : `0x${account.guardian1Sig}`;
|
|
1551
|
+
const sig2 = account.guardian2Sig.startsWith("0x") ? account.guardian2Sig : `0x${account.guardian2Sig}`;
|
|
1552
|
+
deployCalldata = encodeFn(AIRACCOUNT_FACTORY_ABI_PARSED, "createAccountWithDefaults", [
|
|
1553
|
+
account.signerAddress,
|
|
1554
|
+
BigInt(account.salt),
|
|
1555
|
+
account.guardian1,
|
|
1556
|
+
sig1,
|
|
1557
|
+
account.guardian2,
|
|
1558
|
+
sig2,
|
|
1559
|
+
storedDailyLimit
|
|
1560
|
+
]);
|
|
1561
|
+
} else {
|
|
1562
|
+
const minimalConfig = [
|
|
1563
|
+
[viem.zeroAddress, viem.zeroAddress, viem.zeroAddress],
|
|
1564
|
+
// guardians (address[3])
|
|
1565
|
+
EMPTY_P2562,
|
|
1566
|
+
// guardianP256X (bytes32[3]) — v0.20.0
|
|
1567
|
+
EMPTY_P2562,
|
|
1568
|
+
// guardianP256Y (bytes32[3]) — v0.20.0
|
|
1569
|
+
storedDailyLimit,
|
|
1570
|
+
[],
|
|
1571
|
+
// approvedAlgIds
|
|
1572
|
+
0n,
|
|
1573
|
+
// minDailyLimit
|
|
1574
|
+
[],
|
|
1575
|
+
// initialTokens
|
|
1576
|
+
[]
|
|
1577
|
+
// initialTokenConfigs
|
|
1578
|
+
];
|
|
1579
|
+
deployCalldata = encodeFn(AIRACCOUNT_FACTORY_ABI_PARSED, "createAccount", [
|
|
1580
|
+
account.signerAddress,
|
|
1581
|
+
BigInt(account.salt),
|
|
1582
|
+
minimalConfig
|
|
1583
|
+
]);
|
|
1584
|
+
}
|
|
1585
|
+
} else {
|
|
1586
|
+
deployCalldata = encodeFn(FACTORY_ABI_V6_PARSED, "createAccountWithAAStarValidator", [
|
|
1587
|
+
account.signerAddress,
|
|
1588
|
+
account.signerAddress,
|
|
1589
|
+
account.validatorAddress,
|
|
1590
|
+
true,
|
|
1591
|
+
BigInt(account.salt)
|
|
1592
|
+
]);
|
|
1593
|
+
}
|
|
1594
|
+
initCode = viem.concat([factoryAddress, deployCalldata]);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
let callData;
|
|
1598
|
+
if (tokenAddress) {
|
|
1599
|
+
const tokenInfo = await this.tokenService.getTokenInfo(tokenAddress);
|
|
1600
|
+
const transferCalldata = this.tokenService.generateTransferCalldata(
|
|
1601
|
+
to,
|
|
1602
|
+
amount,
|
|
1603
|
+
tokenInfo.decimals
|
|
1604
|
+
);
|
|
1605
|
+
callData = encodeFn(AIRACCOUNT_ABI_PARSED, "execute", [tokenAddress, 0n, transferCalldata]);
|
|
1606
|
+
} else {
|
|
1607
|
+
callData = encodeFn(AIRACCOUNT_ABI_PARSED, "execute", [to, viem.parseEther(amount), data]);
|
|
1608
|
+
}
|
|
1609
|
+
if (wrapExecuteUserOpFlag) {
|
|
1610
|
+
callData = wrapExecuteUserOp(callData);
|
|
1611
|
+
}
|
|
1612
|
+
const gasPrices = await this.ethereum.getUserOperationGasPrice();
|
|
1613
|
+
const isV07 = version === "0.7" /* V0_7 */ || version === "0.8" /* V0_8 */;
|
|
1614
|
+
let baseUserOp;
|
|
1615
|
+
if (isV07) {
|
|
1616
|
+
let factory;
|
|
1617
|
+
let factoryData;
|
|
1618
|
+
if (initCode && initCode !== "0x" && initCode.length > 2) {
|
|
1619
|
+
factory = initCode.slice(0, 42);
|
|
1620
|
+
factoryData = initCode.length > 42 ? "0x" + initCode.slice(42) : "0x";
|
|
1621
|
+
}
|
|
1622
|
+
baseUserOp = {
|
|
1623
|
+
sender,
|
|
1624
|
+
nonce: "0x" + nonce.toString(16),
|
|
1625
|
+
...factory ? { factory, factoryData } : {},
|
|
1626
|
+
callData,
|
|
1627
|
+
callGasLimit: "0x0",
|
|
1628
|
+
verificationGasLimit: "0x0",
|
|
1629
|
+
preVerificationGas: "0x0",
|
|
1630
|
+
maxFeePerGas: gasPrices.maxFeePerGas,
|
|
1631
|
+
maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas,
|
|
1632
|
+
signature: "0x"
|
|
1633
|
+
};
|
|
1634
|
+
} else {
|
|
1635
|
+
baseUserOp = {
|
|
1636
|
+
sender,
|
|
1637
|
+
nonce: "0x" + nonce.toString(16),
|
|
1638
|
+
initCode,
|
|
1639
|
+
callData,
|
|
1640
|
+
callGasLimit: "0x0",
|
|
1641
|
+
verificationGasLimit: "0x0",
|
|
1642
|
+
preVerificationGas: "0x0",
|
|
1643
|
+
maxFeePerGas: gasPrices.maxFeePerGas,
|
|
1644
|
+
maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas,
|
|
1645
|
+
paymasterAndData: "0x",
|
|
1646
|
+
signature: "0x"
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
let paymasterAndData = "0x";
|
|
1650
|
+
if (usePaymaster) {
|
|
1651
|
+
if (paymasterAddress) {
|
|
1652
|
+
const entryPoint = this.ethereum.getEntryPointAddress(version);
|
|
1653
|
+
paymasterAndData = await this.paymasterManager.getPaymasterData(
|
|
1654
|
+
userId,
|
|
1655
|
+
"custom-user-provided",
|
|
1656
|
+
baseUserOp,
|
|
1657
|
+
entryPoint,
|
|
1658
|
+
paymasterAddress,
|
|
1659
|
+
paymasterTokenAddress ? { tokenAddress: paymasterTokenAddress } : void 0
|
|
1660
|
+
);
|
|
1661
|
+
} else {
|
|
1662
|
+
const available = await this.paymasterManager.getAvailablePaymasters(userId);
|
|
1663
|
+
const configured = available.find((pm) => pm.configured);
|
|
1664
|
+
if (configured) {
|
|
1665
|
+
const entryPoint = this.ethereum.getEntryPointAddress(version);
|
|
1666
|
+
paymasterAndData = await this.paymasterManager.getPaymasterData(
|
|
1667
|
+
userId,
|
|
1668
|
+
configured.name,
|
|
1669
|
+
baseUserOp,
|
|
1670
|
+
entryPoint
|
|
1671
|
+
);
|
|
1672
|
+
} else {
|
|
1673
|
+
throw new Error("No paymaster configured and no paymaster address provided");
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
if (!paymasterAndData || paymasterAndData === "0x") {
|
|
1677
|
+
throw new Error(
|
|
1678
|
+
`Paymaster failed to provide sponsorship data. The paymaster at ${paymasterAddress} may not be configured correctly.`
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
if (isV07) {
|
|
1682
|
+
baseUserOp.paymaster = paymasterAndData.slice(0, 42);
|
|
1683
|
+
if (paymasterAndData.length >= 74) {
|
|
1684
|
+
baseUserOp.paymasterVerificationGasLimit = "0x" + BigInt("0x" + paymasterAndData.slice(42, 74)).toString(16);
|
|
1685
|
+
}
|
|
1686
|
+
if (paymasterAndData.length >= 106) {
|
|
1687
|
+
baseUserOp.paymasterPostOpGasLimit = "0x" + BigInt("0x" + paymasterAndData.slice(74, 106)).toString(16);
|
|
1688
|
+
}
|
|
1689
|
+
if (paymasterAndData.length > 106) {
|
|
1690
|
+
baseUserOp.paymasterData = "0x" + paymasterAndData.slice(106);
|
|
1691
|
+
}
|
|
1692
|
+
} else {
|
|
1693
|
+
baseUserOp.paymasterAndData = paymasterAndData;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const gasEstimates = await this.ethereum.estimateUserOperationGas(baseUserOp, version);
|
|
1697
|
+
const standardUserOp = {
|
|
1698
|
+
sender,
|
|
1699
|
+
nonce,
|
|
1700
|
+
initCode,
|
|
1701
|
+
callData,
|
|
1702
|
+
callGasLimit: BigInt(gasEstimates.callGasLimit),
|
|
1703
|
+
verificationGasLimit: BigInt(gasEstimates.verificationGasLimit),
|
|
1704
|
+
preVerificationGas: BigInt(gasEstimates.preVerificationGas),
|
|
1705
|
+
maxFeePerGas: BigInt(gasPrices.maxFeePerGas),
|
|
1706
|
+
maxPriorityFeePerGas: BigInt(gasPrices.maxPriorityFeePerGas),
|
|
1707
|
+
paymasterAndData,
|
|
1708
|
+
signature: "0x"
|
|
1709
|
+
};
|
|
1710
|
+
if (version === "0.7" /* V0_7 */ || version === "0.8" /* V0_8 */) {
|
|
1711
|
+
return chunkXQROKLZI_cjs.ERC4337Utils.packUserOperation(standardUserOp);
|
|
1712
|
+
}
|
|
1713
|
+
return standardUserOp;
|
|
1714
|
+
}
|
|
1715
|
+
formatUserOpForBundler(userOp, version = "0.6" /* V0_6 */) {
|
|
1716
|
+
if (version === "0.7" /* V0_7 */ || version === "0.8" /* V0_8 */) {
|
|
1717
|
+
const packedOp = userOp;
|
|
1718
|
+
const gasLimits = chunkXQROKLZI_cjs.ERC4337Utils.unpackAccountGasLimits(packedOp.accountGasLimits);
|
|
1719
|
+
const gasFees = chunkXQROKLZI_cjs.ERC4337Utils.unpackGasFees(packedOp.gasFees);
|
|
1720
|
+
let factory;
|
|
1721
|
+
let factoryData;
|
|
1722
|
+
if (packedOp.initCode && packedOp.initCode !== "0x" && packedOp.initCode.length > 2) {
|
|
1723
|
+
factory = packedOp.initCode.slice(0, 42);
|
|
1724
|
+
if (packedOp.initCode.length > 42) {
|
|
1725
|
+
factoryData = "0x" + packedOp.initCode.slice(42);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
let paymaster;
|
|
1729
|
+
let paymasterVerificationGasLimit;
|
|
1730
|
+
let paymasterPostOpGasLimit;
|
|
1731
|
+
let paymasterData;
|
|
1732
|
+
if (packedOp.paymasterAndData && packedOp.paymasterAndData !== "0x" && packedOp.paymasterAndData.length > 2) {
|
|
1733
|
+
paymaster = packedOp.paymasterAndData.slice(0, 42);
|
|
1734
|
+
if (packedOp.paymasterAndData.length >= 74) {
|
|
1735
|
+
paymasterVerificationGasLimit = "0x" + BigInt("0x" + packedOp.paymasterAndData.slice(42, 74)).toString(16);
|
|
1736
|
+
}
|
|
1737
|
+
if (packedOp.paymasterAndData.length >= 106) {
|
|
1738
|
+
paymasterPostOpGasLimit = "0x" + BigInt("0x" + packedOp.paymasterAndData.slice(74, 106)).toString(16);
|
|
1739
|
+
}
|
|
1740
|
+
if (packedOp.paymasterAndData.length > 106) {
|
|
1741
|
+
paymasterData = "0x" + packedOp.paymasterAndData.slice(106);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
const result = {
|
|
1745
|
+
sender: packedOp.sender,
|
|
1746
|
+
nonce: typeof packedOp.nonce === "bigint" ? "0x" + packedOp.nonce.toString(16) : packedOp.nonce.toString().startsWith("0x") ? packedOp.nonce.toString() : "0x" + BigInt(packedOp.nonce).toString(16),
|
|
1747
|
+
callData: packedOp.callData,
|
|
1748
|
+
callGasLimit: "0x" + gasLimits.callGasLimit.toString(16),
|
|
1749
|
+
verificationGasLimit: "0x" + gasLimits.verificationGasLimit.toString(16),
|
|
1750
|
+
preVerificationGas: typeof packedOp.preVerificationGas === "bigint" ? "0x" + packedOp.preVerificationGas.toString(16) : packedOp.preVerificationGas.toString().startsWith("0x") ? packedOp.preVerificationGas.toString() : "0x" + BigInt(packedOp.preVerificationGas).toString(16),
|
|
1751
|
+
maxFeePerGas: "0x" + gasFees.maxFeePerGas.toString(16),
|
|
1752
|
+
maxPriorityFeePerGas: "0x" + gasFees.maxPriorityFeePerGas.toString(16),
|
|
1753
|
+
signature: packedOp.signature || "0x"
|
|
1754
|
+
};
|
|
1755
|
+
if (factory) result.factory = factory;
|
|
1756
|
+
if (factoryData) result.factoryData = factoryData;
|
|
1757
|
+
if (paymaster) {
|
|
1758
|
+
result.paymaster = paymaster;
|
|
1759
|
+
result.paymasterVerificationGasLimit = paymasterVerificationGasLimit || "0x30000";
|
|
1760
|
+
result.paymasterPostOpGasLimit = paymasterPostOpGasLimit || "0x30000";
|
|
1761
|
+
if (paymasterData && paymasterData !== "0x") {
|
|
1762
|
+
result.paymasterData = paymasterData;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
return result;
|
|
1766
|
+
}
|
|
1767
|
+
const op = userOp;
|
|
1768
|
+
return {
|
|
1769
|
+
sender: op.sender,
|
|
1770
|
+
nonce: "0x" + op.nonce.toString(16),
|
|
1771
|
+
initCode: op.initCode,
|
|
1772
|
+
callData: op.callData,
|
|
1773
|
+
callGasLimit: "0x" + op.callGasLimit.toString(16),
|
|
1774
|
+
verificationGasLimit: "0x" + op.verificationGasLimit.toString(16),
|
|
1775
|
+
preVerificationGas: "0x" + op.preVerificationGas.toString(16),
|
|
1776
|
+
maxFeePerGas: "0x" + op.maxFeePerGas.toString(16),
|
|
1777
|
+
maxPriorityFeePerGas: "0x" + op.maxPriorityFeePerGas.toString(16),
|
|
1778
|
+
paymasterAndData: op.paymasterAndData,
|
|
1779
|
+
signature: op.signature
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
var DvtPendingConfirmationError = class extends Error {
|
|
1784
|
+
constructor(userOpHash, nodeEndpoint) {
|
|
1785
|
+
super(
|
|
1786
|
+
`DVT node ${nodeEndpoint} withheld its co-signature pending out-of-band confirmation for userOpHash ${userOpHash}; release it via POST /signature/confirm.`
|
|
1787
|
+
);
|
|
1788
|
+
this.userOpHash = userOpHash;
|
|
1789
|
+
this.nodeEndpoint = nodeEndpoint;
|
|
1790
|
+
this.name = "DvtPendingConfirmationError";
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
function isPendingConfirmation(data) {
|
|
1794
|
+
return typeof data === "object" && data !== null && data.status === "pending_confirmation";
|
|
1795
|
+
}
|
|
1796
|
+
var BLSSignatureService = class {
|
|
1797
|
+
constructor(config, ethereum, storage, signer, logger) {
|
|
1798
|
+
this.config = config;
|
|
1799
|
+
this.ethereum = ethereum;
|
|
1800
|
+
this.storage = storage;
|
|
1801
|
+
this.signer = signer;
|
|
1802
|
+
this.logger = logger ?? new ConsoleLogger("[BLSSignatureService]");
|
|
1803
|
+
}
|
|
1804
|
+
blsManager = null;
|
|
1805
|
+
logger;
|
|
1806
|
+
/** Lazy-initialize BLSManager on first use. */
|
|
1807
|
+
async ensureInitialized() {
|
|
1808
|
+
if (this.blsManager) return this.blsManager;
|
|
1809
|
+
const blsConfig = await this.storage.getBlsConfig();
|
|
1810
|
+
const seedNodes = this.config.blsSeedNodes ?? blsConfig?.discovery?.seedNodes?.map((n) => n.endpoint) ?? [];
|
|
1811
|
+
this.blsManager = new chunkXQROKLZI_cjs.BLSManager({
|
|
1812
|
+
seedNodes,
|
|
1813
|
+
discoveryTimeout: this.config.blsDiscoveryTimeout ?? 1e4
|
|
1814
|
+
});
|
|
1815
|
+
return this.blsManager;
|
|
1816
|
+
}
|
|
1817
|
+
async getActiveSignerNodes() {
|
|
1818
|
+
const manager = await this.ensureInitialized();
|
|
1819
|
+
const nodes = await manager.getAvailableNodes();
|
|
1820
|
+
if (nodes.length > 0) {
|
|
1821
|
+
try {
|
|
1822
|
+
await this.storage.updateSignerNodesCache(nodes);
|
|
1823
|
+
} catch {
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return nodes;
|
|
1827
|
+
}
|
|
1828
|
+
async generateBLSSignature(userId, userOpHash, ctx) {
|
|
1829
|
+
const manager = await this.ensureInitialized();
|
|
1830
|
+
const activeNodes = await this.getActiveSignerNodes();
|
|
1831
|
+
if (activeNodes.length < 1) {
|
|
1832
|
+
throw new Error("No active BLS signer nodes available");
|
|
1833
|
+
}
|
|
1834
|
+
const selectedNodes = activeNodes.slice(0, Math.min(3, activeNodes.length));
|
|
1835
|
+
const signerNodeSignatures = [];
|
|
1836
|
+
const signerNodeIds = [];
|
|
1837
|
+
for (const node of selectedNodes) {
|
|
1838
|
+
try {
|
|
1839
|
+
const response = await axios__default.default.post(`${node.apiEndpoint}/signature/sign`, {
|
|
1840
|
+
message: userOpHash
|
|
1841
|
+
});
|
|
1842
|
+
if (isPendingConfirmation(response.data)) {
|
|
1843
|
+
throw new DvtPendingConfirmationError(response.data.userOpHash ?? userOpHash, node.apiEndpoint);
|
|
1844
|
+
}
|
|
1845
|
+
const signatureForAggregation = response.data.signatureCompact || response.data.signature;
|
|
1846
|
+
const formatted = signatureForAggregation.startsWith("0x") ? signatureForAggregation : `0x${signatureForAggregation}`;
|
|
1847
|
+
signerNodeSignatures.push(formatted);
|
|
1848
|
+
signerNodeIds.push(response.data.nodeId);
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
if (err instanceof DvtPendingConfirmationError) throw err;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (signerNodeSignatures.length === 0) {
|
|
1854
|
+
throw new Error("Failed to get signatures from any BLS signer nodes");
|
|
1855
|
+
}
|
|
1856
|
+
let aggregatedSignature;
|
|
1857
|
+
if (signerNodeSignatures.length > 1) {
|
|
1858
|
+
const aggregateResponse = await axios__default.default.post(
|
|
1859
|
+
`${selectedNodes[0].apiEndpoint}/signature/aggregate`,
|
|
1860
|
+
{ signatures: signerNodeSignatures }
|
|
1861
|
+
);
|
|
1862
|
+
aggregatedSignature = aggregateResponse.data.signature.startsWith("0x") ? aggregateResponse.data.signature : `0x${aggregateResponse.data.signature}`;
|
|
1863
|
+
} else {
|
|
1864
|
+
const singleSignResponse = await axios__default.default.post(
|
|
1865
|
+
`${selectedNodes[0].apiEndpoint}/signature/sign`,
|
|
1866
|
+
{ message: userOpHash }
|
|
1867
|
+
);
|
|
1868
|
+
if (isPendingConfirmation(singleSignResponse.data)) {
|
|
1869
|
+
throw new DvtPendingConfirmationError(
|
|
1870
|
+
singleSignResponse.data.userOpHash ?? userOpHash,
|
|
1871
|
+
selectedNodes[0].apiEndpoint
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
aggregatedSignature = singleSignResponse.data.signature.startsWith("0x") ? singleSignResponse.data.signature : `0x${singleSignResponse.data.signature}`;
|
|
1875
|
+
}
|
|
1876
|
+
const messagePoint = await manager.generateMessagePoint(userOpHash);
|
|
1877
|
+
const account = await this.storage.findAccountByUserId(userId);
|
|
1878
|
+
if (!account) {
|
|
1879
|
+
throw new Error(`User account not found for userId: ${userId}`);
|
|
1880
|
+
}
|
|
1881
|
+
const walletAddress = await this.signer.getAddress(userId);
|
|
1882
|
+
if (walletAddress.toLowerCase() !== account.signerAddress.toLowerCase()) {
|
|
1883
|
+
throw new Error(
|
|
1884
|
+
`Wallet address mismatch! Wallet: ${walletAddress}, Expected: ${account.signerAddress}`
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
const aaSignature = await this.signer.signMessage(
|
|
1888
|
+
userId,
|
|
1889
|
+
viem.hexToBytes(userOpHash),
|
|
1890
|
+
ctx
|
|
1891
|
+
);
|
|
1892
|
+
const messagePointHash = chunkXQROKLZI_cjs.keccak256(messagePoint);
|
|
1893
|
+
const messagePointSignature = await this.signer.signMessage(
|
|
1894
|
+
userId,
|
|
1895
|
+
viem.hexToBytes(messagePointHash),
|
|
1896
|
+
ctx
|
|
1897
|
+
);
|
|
1898
|
+
return {
|
|
1899
|
+
nodeIds: signerNodeIds,
|
|
1900
|
+
signature: aggregatedSignature,
|
|
1901
|
+
messagePoint,
|
|
1902
|
+
aaAddress: account.signerAddress,
|
|
1903
|
+
aaSignature,
|
|
1904
|
+
messagePointSignature
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
async packSignature(blsData) {
|
|
1908
|
+
const manager = await this.ensureInitialized();
|
|
1909
|
+
return manager.packSignature(blsData);
|
|
1910
|
+
}
|
|
1911
|
+
// ── Tiered Signature Support (M4) ─────────────────────────────
|
|
1912
|
+
/**
|
|
1913
|
+
* Generate a tiered signature based on the required tier level.
|
|
1914
|
+
*
|
|
1915
|
+
* - Tier 1: raw 65-byte ECDSA (no algId prefix, backwards-compat)
|
|
1916
|
+
* - Tier 2: algId 0x04 — P256 + BLS aggregate + messagePoint ECDSA
|
|
1917
|
+
* - Tier 3: algId 0x05 — P256 + BLS + messagePoint ECDSA + Guardian ECDSA
|
|
1918
|
+
*
|
|
1919
|
+
* @param tier - Required tier level (1, 2, or 3)
|
|
1920
|
+
* @param userId - User ID for account lookup
|
|
1921
|
+
* @param userOpHash - The UserOp hash to sign
|
|
1922
|
+
* @param p256Signature - P256 passkey signature (64 bytes, required for tier 2/3)
|
|
1923
|
+
* @param guardianSigner - Guardian signer (required for tier 3)
|
|
1924
|
+
* @param ctx - Optional passkey assertion context for KMS signing
|
|
1925
|
+
*/
|
|
1926
|
+
async generateTieredSignature(params) {
|
|
1927
|
+
const { tier, userId, userOpHash, p256Signature, guardianSigner, ctx } = params;
|
|
1928
|
+
const manager = await this.ensureInitialized();
|
|
1929
|
+
if (tier === 1) {
|
|
1930
|
+
const account = await this.storage.findAccountByUserId(userId);
|
|
1931
|
+
if (!account) throw new Error(`User account not found for userId: ${userId}`);
|
|
1932
|
+
return this.signer.signMessage(userId, viem.hexToBytes(userOpHash), ctx);
|
|
1933
|
+
}
|
|
1934
|
+
if (!p256Signature) {
|
|
1935
|
+
throw new Error(`P256 signature required for Tier ${tier}`);
|
|
1936
|
+
}
|
|
1937
|
+
const blsData = await this.generateBLSSignature(userId, userOpHash, ctx);
|
|
1938
|
+
if (tier === 2) {
|
|
1939
|
+
const t2Data = {
|
|
1940
|
+
p256Signature,
|
|
1941
|
+
nodeIds: blsData.nodeIds,
|
|
1942
|
+
blsSignature: blsData.signature,
|
|
1943
|
+
messagePoint: blsData.messagePoint,
|
|
1944
|
+
messagePointSignature: blsData.messagePointSignature
|
|
1945
|
+
};
|
|
1946
|
+
return manager.packCumulativeT2Signature(t2Data);
|
|
1947
|
+
}
|
|
1948
|
+
if (!guardianSigner) {
|
|
1949
|
+
throw new Error("Guardian signer required for Tier 3");
|
|
1950
|
+
}
|
|
1951
|
+
const guardianSignature = await guardianSigner.signMessage(
|
|
1952
|
+
viem.hexToBytes(userOpHash)
|
|
1953
|
+
);
|
|
1954
|
+
const t3Data = {
|
|
1955
|
+
p256Signature,
|
|
1956
|
+
nodeIds: blsData.nodeIds,
|
|
1957
|
+
blsSignature: blsData.signature,
|
|
1958
|
+
messagePoint: blsData.messagePoint,
|
|
1959
|
+
messagePointSignature: blsData.messagePointSignature,
|
|
1960
|
+
guardianSignature
|
|
1961
|
+
};
|
|
1962
|
+
return manager.packCumulativeT3Signature(t3Data);
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
var ERC20_ABI_PARSED = viem.parseAbi(ERC20_ABI);
|
|
1966
|
+
var TokenService = class {
|
|
1967
|
+
constructor(ethereum) {
|
|
1968
|
+
this.ethereum = ethereum;
|
|
1969
|
+
}
|
|
1970
|
+
async getTokenInfo(tokenAddress) {
|
|
1971
|
+
const client = this.ethereum.getProvider();
|
|
1972
|
+
const address = tokenAddress;
|
|
1973
|
+
const [name, symbol, decimals] = await Promise.all([
|
|
1974
|
+
client.readContract({ address, abi: ERC20_ABI_PARSED, functionName: "name" }),
|
|
1975
|
+
client.readContract({ address, abi: ERC20_ABI_PARSED, functionName: "symbol" }),
|
|
1976
|
+
client.readContract({ address, abi: ERC20_ABI_PARSED, functionName: "decimals" })
|
|
1977
|
+
]);
|
|
1978
|
+
return {
|
|
1979
|
+
address: tokenAddress.toLowerCase(),
|
|
1980
|
+
name,
|
|
1981
|
+
symbol,
|
|
1982
|
+
decimals: Number(decimals)
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
async getTokenBalance(tokenAddress, walletAddress) {
|
|
1986
|
+
const client = this.ethereum.getProvider();
|
|
1987
|
+
try {
|
|
1988
|
+
const balance = await client.readContract({
|
|
1989
|
+
address: tokenAddress,
|
|
1990
|
+
abi: ERC20_ABI_PARSED,
|
|
1991
|
+
functionName: "balanceOf",
|
|
1992
|
+
args: [walletAddress]
|
|
1993
|
+
});
|
|
1994
|
+
return balance.toString();
|
|
1995
|
+
} catch {
|
|
1996
|
+
return "0";
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
async getFormattedTokenBalance(tokenAddress, walletAddress) {
|
|
2000
|
+
const tokenInfo = await this.getTokenInfo(tokenAddress);
|
|
2001
|
+
const rawBalance = await this.getTokenBalance(tokenAddress, walletAddress);
|
|
2002
|
+
const formattedBalance = viem.formatUnits(BigInt(rawBalance), tokenInfo.decimals);
|
|
2003
|
+
return { token: tokenInfo, balance: rawBalance, formattedBalance };
|
|
2004
|
+
}
|
|
2005
|
+
generateTransferCalldata(to, amount, decimals) {
|
|
2006
|
+
const parsedAmount = viem.parseUnits(amount, decimals);
|
|
2007
|
+
return viem.encodeFunctionData({
|
|
2008
|
+
abi: ERC20_ABI_PARSED,
|
|
2009
|
+
functionName: "transfer",
|
|
2010
|
+
args: [to, parsedAmount]
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
async validateToken(tokenAddress) {
|
|
2014
|
+
try {
|
|
2015
|
+
const client = this.ethereum.getProvider();
|
|
2016
|
+
const address = tokenAddress;
|
|
2017
|
+
const [name, symbol, decimals] = await Promise.race([
|
|
2018
|
+
Promise.all([
|
|
2019
|
+
client.readContract({ address, abi: ERC20_ABI_PARSED, functionName: "name" }),
|
|
2020
|
+
client.readContract({ address, abi: ERC20_ABI_PARSED, functionName: "symbol" }),
|
|
2021
|
+
client.readContract({ address, abi: ERC20_ABI_PARSED, functionName: "decimals" })
|
|
2022
|
+
]),
|
|
2023
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1e4))
|
|
2024
|
+
]);
|
|
2025
|
+
return {
|
|
2026
|
+
isValid: true,
|
|
2027
|
+
token: {
|
|
2028
|
+
address: tokenAddress.toLowerCase(),
|
|
2029
|
+
name,
|
|
2030
|
+
symbol,
|
|
2031
|
+
decimals: Number(decimals)
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
return {
|
|
2036
|
+
isValid: false,
|
|
2037
|
+
error: error instanceof Error ? error.message : "Invalid ERC20 token"
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
// ../airaccount/src/server/services/wallet-manager.ts
|
|
2044
|
+
var WalletManager = class {
|
|
2045
|
+
constructor(signer) {
|
|
2046
|
+
this.signer = signer;
|
|
2047
|
+
}
|
|
2048
|
+
async getAddress(userId) {
|
|
2049
|
+
return this.signer.getAddress(userId);
|
|
2050
|
+
}
|
|
2051
|
+
async signMessage(userId, message, ctx) {
|
|
2052
|
+
return this.signer.signMessage(userId, message, ctx);
|
|
2053
|
+
}
|
|
2054
|
+
async ensureSigner(userId) {
|
|
2055
|
+
return this.signer.ensureSigner(userId);
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
|
|
2059
|
+
// ../airaccount/src/server/server-client.ts
|
|
2060
|
+
var AirAccountServerClient = class {
|
|
2061
|
+
ethereum;
|
|
2062
|
+
accounts;
|
|
2063
|
+
transfers;
|
|
2064
|
+
bls;
|
|
2065
|
+
paymaster;
|
|
2066
|
+
tokens;
|
|
2067
|
+
wallets;
|
|
2068
|
+
constructor(config) {
|
|
2069
|
+
validateConfig(config);
|
|
2070
|
+
const logger = config.logger ?? new ConsoleLogger("[YAAA]");
|
|
2071
|
+
this.ethereum = new EthereumProvider(config);
|
|
2072
|
+
this.wallets = new WalletManager(config.signer);
|
|
2073
|
+
this.tokens = new TokenService(this.ethereum);
|
|
2074
|
+
this.paymaster = new PaymasterManager(this.ethereum, config.storage, logger);
|
|
2075
|
+
this.accounts = new AccountManager(this.ethereum, config.storage, config.signer, logger);
|
|
2076
|
+
this.bls = new BLSSignatureService(
|
|
2077
|
+
config,
|
|
2078
|
+
this.ethereum,
|
|
2079
|
+
config.storage,
|
|
2080
|
+
config.signer,
|
|
2081
|
+
logger
|
|
2082
|
+
);
|
|
2083
|
+
this.transfers = new TransferManager(
|
|
2084
|
+
this.ethereum,
|
|
2085
|
+
this.accounts,
|
|
2086
|
+
this.bls,
|
|
2087
|
+
this.paymaster,
|
|
2088
|
+
this.tokens,
|
|
2089
|
+
config.storage,
|
|
2090
|
+
config.signer,
|
|
2091
|
+
logger
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
var YAAAServerClient = AirAccountServerClient;
|
|
2096
|
+
function hashMessage(message) {
|
|
2097
|
+
if (typeof message === "string") return viem.hashMessage(message);
|
|
2098
|
+
return viem.hashMessage({ raw: message });
|
|
2099
|
+
}
|
|
2100
|
+
async function recoverAddress(hash, signature) {
|
|
2101
|
+
return viem.recoverAddress({ hash, signature });
|
|
2102
|
+
}
|
|
2103
|
+
function buildAuthorizationHash(chainId, nonce, delegateAddress) {
|
|
2104
|
+
const encoded = viem.toRlp([
|
|
2105
|
+
chainId === 0 ? "0x" : viem.numberToHex(chainId),
|
|
2106
|
+
delegateAddress,
|
|
2107
|
+
nonce === 0n ? "0x" : viem.numberToHex(nonce)
|
|
2108
|
+
]);
|
|
2109
|
+
return viem.keccak256(viem.concatHex(["0x05", encoded]));
|
|
2110
|
+
}
|
|
2111
|
+
async function verifyAuthorization(eoa, chainId, nonce, signature, delegateAddress) {
|
|
2112
|
+
const hash = buildAuthorizationHash(chainId, nonce, delegateAddress);
|
|
2113
|
+
const recovered = await recoverAddress(hash, signature);
|
|
2114
|
+
return recovered.toLowerCase() === eoa.toLowerCase();
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// ../airaccount/src/server/services/module-manager.ts
|
|
2118
|
+
function buildInstallModuleHash(chainId, account, moduleTypeId, module, moduleInitData = "0x") {
|
|
2119
|
+
const moduleInitDataHash = chunkXQROKLZI_cjs.keccak256(moduleInitData);
|
|
2120
|
+
const raw = chunkXQROKLZI_cjs.keccak256(
|
|
2121
|
+
chunkXQROKLZI_cjs.solidityPacked(
|
|
2122
|
+
["string", "uint256", "address", "uint256", "address", "bytes32"],
|
|
2123
|
+
["INSTALL_MODULE", BigInt(chainId), account, BigInt(moduleTypeId), module, moduleInitDataHash]
|
|
2124
|
+
)
|
|
2125
|
+
);
|
|
2126
|
+
return hashMessage(viem.hexToBytes(raw));
|
|
2127
|
+
}
|
|
2128
|
+
function buildUninstallModuleHash(chainId, account, moduleTypeId, module) {
|
|
2129
|
+
const raw = chunkXQROKLZI_cjs.keccak256(
|
|
2130
|
+
chunkXQROKLZI_cjs.solidityPacked(
|
|
2131
|
+
["string", "uint256", "address", "uint256", "address"],
|
|
2132
|
+
["UNINSTALL_MODULE", BigInt(chainId), account, BigInt(moduleTypeId), module]
|
|
2133
|
+
)
|
|
2134
|
+
);
|
|
2135
|
+
return hashMessage(viem.hexToBytes(raw));
|
|
2136
|
+
}
|
|
2137
|
+
var ModuleManager = class {
|
|
2138
|
+
provider;
|
|
2139
|
+
chainId;
|
|
2140
|
+
constructor(provider, chainId) {
|
|
2141
|
+
this.provider = provider;
|
|
2142
|
+
this.chainId = chainId;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Encode calldata for installModule().
|
|
2146
|
+
* Caller is responsible for submitting via UserOp (EntryPoint) or direct tx.
|
|
2147
|
+
*/
|
|
2148
|
+
encodeInstall(params) {
|
|
2149
|
+
const sigs = params.guardianSigs ?? [];
|
|
2150
|
+
const initData = params.moduleInitData ?? "0x";
|
|
2151
|
+
const packed = sigs.length > 0 ? viem.concat([...sigs, initData]) : initData;
|
|
2152
|
+
return viem.encodeFunctionData({
|
|
2153
|
+
abi: viem.parseAbi(AIRACCOUNT_ABI),
|
|
2154
|
+
functionName: "installModule",
|
|
2155
|
+
args: [BigInt(params.moduleTypeId), params.module, packed]
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Encode calldata for uninstallModule().
|
|
2160
|
+
* Always requires 2 guardian signatures.
|
|
2161
|
+
*/
|
|
2162
|
+
encodeUninstall(params) {
|
|
2163
|
+
const deInitData = params.moduleDeInitData ?? "0x";
|
|
2164
|
+
const packed = viem.concat([
|
|
2165
|
+
params.guardianSig1,
|
|
2166
|
+
params.guardianSig2,
|
|
2167
|
+
deInitData
|
|
2168
|
+
]);
|
|
2169
|
+
return viem.encodeFunctionData({
|
|
2170
|
+
abi: viem.parseAbi(AIRACCOUNT_ABI),
|
|
2171
|
+
functionName: "uninstallModule",
|
|
2172
|
+
args: [BigInt(params.moduleTypeId), params.module, packed]
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
/** Check if a module is currently installed on the account. */
|
|
2176
|
+
async isInstalled(account, moduleTypeId, module) {
|
|
2177
|
+
return await this.provider.readContract({
|
|
2178
|
+
address: account,
|
|
2179
|
+
abi: viem.parseAbi(AIRACCOUNT_ABI),
|
|
2180
|
+
functionName: "isModuleInstalled",
|
|
2181
|
+
args: [BigInt(moduleTypeId), module, "0x"]
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
/** Return the install hash for a guardian to sign (r5 format, includes moduleInitData hash). */
|
|
2185
|
+
installHash(account, moduleTypeId, module, moduleInitData = "0x") {
|
|
2186
|
+
return buildInstallModuleHash(this.chainId, account, moduleTypeId, module, moduleInitData);
|
|
2187
|
+
}
|
|
2188
|
+
/** Return the uninstall hash for guardians to sign. */
|
|
2189
|
+
uninstallHash(account, moduleTypeId, module) {
|
|
2190
|
+
return buildUninstallModuleHash(this.chainId, account, moduleTypeId, module);
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Convenience: build install calldata for the standard M7 module set.
|
|
2194
|
+
* Uses pre-deployed Sepolia addresses (r4 audit-final). No guardian sigs required when
|
|
2195
|
+
* account threshold <= 40 (default for newly created accounts).
|
|
2196
|
+
*
|
|
2197
|
+
* Note: beta.3 unifies these into SessionKeyValidator. This helper retains the r4
|
|
2198
|
+
* addresses for accounts already deployed on r4; new accounts use SessionKeyValidator.
|
|
2199
|
+
*/
|
|
2200
|
+
encodeInstallDefaultModules(account) {
|
|
2201
|
+
const addresses = AIRACCOUNT_ADDRESSES.sepolia;
|
|
2202
|
+
return {
|
|
2203
|
+
compositeValidator: this.encodeInstall({
|
|
2204
|
+
account,
|
|
2205
|
+
moduleTypeId: MODULE_TYPE.VALIDATOR,
|
|
2206
|
+
module: addresses.compositeValidatorM7r4
|
|
2207
|
+
}),
|
|
2208
|
+
tierGuardHook: this.encodeInstall({
|
|
2209
|
+
account,
|
|
2210
|
+
moduleTypeId: MODULE_TYPE.HOOK,
|
|
2211
|
+
module: addresses.tierGuardHookM7r4
|
|
2212
|
+
})
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
var SESSION_KEY_VALIDATOR_VIEM_ABI = viem.parseAbi(SESSION_KEY_VALIDATOR_ABI);
|
|
2217
|
+
var AGENT_SESSION_KEY_VALIDATOR_VIEM_ABI = viem.parseAbi(AGENT_SESSION_KEY_VALIDATOR_ABI);
|
|
2218
|
+
function buildSessionStruct(params) {
|
|
2219
|
+
return {
|
|
2220
|
+
expiry: params.expiry,
|
|
2221
|
+
contractScope: params.contractScope ?? viem.zeroAddress,
|
|
2222
|
+
selectorScope: params.selectorScope ?? "0x00000000",
|
|
2223
|
+
revoked: false,
|
|
2224
|
+
velocityLimit: params.velocityLimit ?? 0,
|
|
2225
|
+
velocityWindow: params.velocityWindow ?? 0,
|
|
2226
|
+
callTargets: params.callTargets ?? [],
|
|
2227
|
+
selectorAllowlist: params.selectorAllowlist ?? []
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
function decodeSessionInfo(session) {
|
|
2231
|
+
const s = session;
|
|
2232
|
+
const expiry = Number(s.expiry);
|
|
2233
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2234
|
+
return {
|
|
2235
|
+
expiry,
|
|
2236
|
+
contractScope: s.contractScope,
|
|
2237
|
+
selectorScope: s.selectorScope,
|
|
2238
|
+
revoked: s.revoked,
|
|
2239
|
+
velocityLimit: Number(s.velocityLimit),
|
|
2240
|
+
velocityWindow: Number(s.velocityWindow),
|
|
2241
|
+
callTargets: [...s.callTargets ?? []],
|
|
2242
|
+
selectorAllowlist: [...s.selectorAllowlist ?? []],
|
|
2243
|
+
active: expiry > now && !s.revoked
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
var SessionKeyService = class {
|
|
2247
|
+
skValidator;
|
|
2248
|
+
askValidator;
|
|
2249
|
+
constructor(provider, sessionKeyValidatorAddress, agentSessionKeyValidatorAddress) {
|
|
2250
|
+
this.skValidator = viem.getContract({
|
|
2251
|
+
address: sessionKeyValidatorAddress,
|
|
2252
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2253
|
+
client: provider
|
|
2254
|
+
});
|
|
2255
|
+
this.askValidator = viem.getContract({
|
|
2256
|
+
address: agentSessionKeyValidatorAddress,
|
|
2257
|
+
abi: AGENT_SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2258
|
+
client: provider
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
// ── M6: Basic Session Keys ────────────────────────────────────
|
|
2262
|
+
/**
|
|
2263
|
+
* Build the hash that the account owner must sign to grant a session key.
|
|
2264
|
+
* Use grantSession() with this sig, or grantSessionDirect() from the account itself.
|
|
2265
|
+
*/
|
|
2266
|
+
async buildGrantHash(params) {
|
|
2267
|
+
return readBuildGrantHash(
|
|
2268
|
+
this.skValidator,
|
|
2269
|
+
params.account,
|
|
2270
|
+
params.sessionKey,
|
|
2271
|
+
buildSessionStruct(params)
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
/** Query an ECDSA session key state (decodes the 8-field Session tuple). */
|
|
2275
|
+
async getSession(account, sessionKey) {
|
|
2276
|
+
const session = await this.skValidator.read.getSession([account, sessionKey]);
|
|
2277
|
+
return decodeSessionInfo(session);
|
|
2278
|
+
}
|
|
2279
|
+
/** Check if an ECDSA session is currently active. */
|
|
2280
|
+
async isSessionActive(account, sessionKey) {
|
|
2281
|
+
return this.skValidator.read.isSessionActive([account, sessionKey]);
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Encode calldata for session grant.
|
|
2285
|
+
*
|
|
2286
|
+
* - **With ownerSig** → `grantSession()` — for gasless/UserOp flows.
|
|
2287
|
+
* Owner signs the GRANT_SESSION_V2 typed hash via KMS `sign-grant-session`,
|
|
2288
|
+
* then the relayer calls `grantSession(account, key, cfg, ownerSig)` on-chain.
|
|
2289
|
+
* This is the ONLY path for ERC-4337 sponsored / gasless grant flows.
|
|
2290
|
+
*
|
|
2291
|
+
* - **Without ownerSig** → `grantSessionDirect()` — **owner EOA direct-send only**.
|
|
2292
|
+
* Since v0.17.2 round 3, `grantSessionDirect` requires `msg.sender == ownerOf(account)`.
|
|
2293
|
+
* It does NOT accept `msg.sender == account` (removed in round 3 — confused-deputy fix).
|
|
2294
|
+
* Do NOT encode this for a UserOp callData; the EntryPoint is not the owner EOA.
|
|
2295
|
+
*/
|
|
2296
|
+
encodeGrantSession(params) {
|
|
2297
|
+
const cfg = buildSessionStruct(params);
|
|
2298
|
+
if (params.ownerSig) {
|
|
2299
|
+
return viem.encodeFunctionData({
|
|
2300
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2301
|
+
functionName: "grantSession",
|
|
2302
|
+
args: [params.account, params.sessionKey, cfg, params.ownerSig]
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
return viem.encodeFunctionData({
|
|
2306
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2307
|
+
functionName: "grantSessionDirect",
|
|
2308
|
+
args: [params.account, params.sessionKey, cfg]
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
/** Encode calldata for revokeSession(). */
|
|
2312
|
+
encodeRevokeSession(account, sessionKey) {
|
|
2313
|
+
return viem.encodeFunctionData({
|
|
2314
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2315
|
+
functionName: "revokeSession",
|
|
2316
|
+
args: [account, sessionKey]
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
// ── M6: P256 / Passkey Session Keys ───────────────────────────
|
|
2320
|
+
/**
|
|
2321
|
+
* Build the hash that the account owner must sign to grant a P256/passkey session key.
|
|
2322
|
+
* Use grantP256Session() with this sig, or grantP256SessionDirect() from the owner EOA itself.
|
|
2323
|
+
* The owner/KMS signs this hash to authorize a gasless grantP256Session().
|
|
2324
|
+
*/
|
|
2325
|
+
async buildP256GrantHash(params) {
|
|
2326
|
+
return readBuildP256GrantHash(
|
|
2327
|
+
this.skValidator,
|
|
2328
|
+
params.account,
|
|
2329
|
+
params.keyX,
|
|
2330
|
+
params.keyY,
|
|
2331
|
+
buildSessionStruct(params)
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Query a P256 session key state (decodes the 8-field Session tuple).
|
|
2336
|
+
* @param keyHash The keccak256 hash of (keyX, keyY) used as the on-chain session id.
|
|
2337
|
+
*/
|
|
2338
|
+
async getP256Session(account, keyHash) {
|
|
2339
|
+
const session = await this.skValidator.read.getP256Session([account, keyHash]);
|
|
2340
|
+
return decodeSessionInfo(session);
|
|
2341
|
+
}
|
|
2342
|
+
/** Check if a P256 session is currently active. */
|
|
2343
|
+
async isP256SessionActive(account, keyX, keyY) {
|
|
2344
|
+
return this.skValidator.read.isP256SessionActive([account, keyX, keyY]);
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Encode calldata for a P256/passkey session grant.
|
|
2348
|
+
*
|
|
2349
|
+
* - **With ownerSig** → `grantP256Session()` — for gasless/UserOp flows.
|
|
2350
|
+
* Owner signs the buildP256GrantHash() digest via KMS `sign-p256-grant-session`,
|
|
2351
|
+
* then the relayer calls `grantP256Session(account, keyX, keyY, cfg, ownerSig)` on-chain.
|
|
2352
|
+
* This is the ONLY path for ERC-4337 sponsored / gasless P256 grant flows.
|
|
2353
|
+
*
|
|
2354
|
+
* - **Without ownerSig** → `grantP256SessionDirect()` — **owner EOA direct-send only**.
|
|
2355
|
+
* Since v0.17.2 round 3, `grantP256SessionDirect` requires `msg.sender == ownerOf(account)`.
|
|
2356
|
+
* It does NOT accept `msg.sender == account` (removed in round 3 — confused-deputy fix).
|
|
2357
|
+
* Do NOT encode this for a UserOp callData; the EntryPoint is not the owner EOA.
|
|
2358
|
+
*/
|
|
2359
|
+
encodeGrantP256Session(params) {
|
|
2360
|
+
const cfg = buildSessionStruct(params);
|
|
2361
|
+
if (params.ownerSig) {
|
|
2362
|
+
return viem.encodeFunctionData({
|
|
2363
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2364
|
+
functionName: "grantP256Session",
|
|
2365
|
+
args: [params.account, params.keyX, params.keyY, cfg, params.ownerSig]
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
return viem.encodeFunctionData({
|
|
2369
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2370
|
+
functionName: "grantP256SessionDirect",
|
|
2371
|
+
args: [params.account, params.keyX, params.keyY, cfg]
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
/** Encode calldata for revokeP256Session(). */
|
|
2375
|
+
encodeRevokeP256Session(account, keyX, keyY) {
|
|
2376
|
+
return viem.encodeFunctionData({
|
|
2377
|
+
abi: SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2378
|
+
functionName: "revokeP256Session",
|
|
2379
|
+
args: [account, keyX, keyY]
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
// ── M7: Agent Session Keys ────────────────────────────────────
|
|
2383
|
+
/**
|
|
2384
|
+
* Encode calldata for grantAgentSession().
|
|
2385
|
+
* Must be called from the account (via UserOp or direct execute).
|
|
2386
|
+
* The contract uses msg.sender as the account — no account param needed.
|
|
2387
|
+
*/
|
|
2388
|
+
encodeGrantAgentSession(sessionKey, cfg) {
|
|
2389
|
+
return viem.encodeFunctionData({
|
|
2390
|
+
abi: AGENT_SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2391
|
+
functionName: "grantAgentSession",
|
|
2392
|
+
args: [
|
|
2393
|
+
sessionKey,
|
|
2394
|
+
{
|
|
2395
|
+
expiry: cfg.expiry,
|
|
2396
|
+
velocityLimit: cfg.velocityLimit,
|
|
2397
|
+
velocityWindow: cfg.velocityWindow,
|
|
2398
|
+
revoked: false,
|
|
2399
|
+
callTargets: cfg.callTargets,
|
|
2400
|
+
selectorAllowlist: cfg.selectorAllowlist
|
|
2401
|
+
}
|
|
2402
|
+
]
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Encode calldata for delegateSession() — sub-agent delegation.
|
|
2407
|
+
* The sub-agent config must be a strict subset of the parent session's scope.
|
|
2408
|
+
* Called by the parent session key (not the account owner).
|
|
2409
|
+
* @param account The smart account under which the parent session was granted.
|
|
2410
|
+
*/
|
|
2411
|
+
encodeDelegateSession(account, subKey, subCfg) {
|
|
2412
|
+
return viem.encodeFunctionData({
|
|
2413
|
+
abi: AGENT_SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2414
|
+
functionName: "delegateSession",
|
|
2415
|
+
args: [
|
|
2416
|
+
account,
|
|
2417
|
+
subKey,
|
|
2418
|
+
{
|
|
2419
|
+
expiry: subCfg.expiry,
|
|
2420
|
+
velocityLimit: subCfg.velocityLimit,
|
|
2421
|
+
velocityWindow: subCfg.velocityWindow,
|
|
2422
|
+
revoked: false,
|
|
2423
|
+
callTargets: subCfg.callTargets,
|
|
2424
|
+
selectorAllowlist: subCfg.selectorAllowlist
|
|
2425
|
+
}
|
|
2426
|
+
]
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
/** Encode calldata for revokeAgentSession(). */
|
|
2430
|
+
encodeRevokeAgentSession(sessionKey) {
|
|
2431
|
+
return viem.encodeFunctionData({
|
|
2432
|
+
abi: AGENT_SESSION_KEY_VALIDATOR_VIEM_ABI,
|
|
2433
|
+
functionName: "revokeAgentSession",
|
|
2434
|
+
args: [sessionKey]
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
/** Query agent session config + runtime state. */
|
|
2438
|
+
async getAgentSession(account, sessionKey) {
|
|
2439
|
+
const [expiry, velocityLimit, velocityWindow, revoked, callTargets, selectorAllowlist] = await this.askValidator.read.agentSessions([account, sessionKey]);
|
|
2440
|
+
const [callCount, windowStart] = await this.askValidator.read.sessionStates([account, sessionKey]);
|
|
2441
|
+
return {
|
|
2442
|
+
expiry: Number(expiry),
|
|
2443
|
+
velocityLimit: Number(velocityLimit),
|
|
2444
|
+
velocityWindow: Number(velocityWindow),
|
|
2445
|
+
callTargets,
|
|
2446
|
+
selectorAllowlist,
|
|
2447
|
+
revoked,
|
|
2448
|
+
callCount: BigInt(callCount),
|
|
2449
|
+
windowStart: BigInt(windowStart)
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
/** Check if an agent session is active (not expired, not revoked). */
|
|
2453
|
+
async isAgentSessionActive(account, sessionKey) {
|
|
2454
|
+
const session = await this.getAgentSession(account, sessionKey);
|
|
2455
|
+
return session.expiry > Math.floor(Date.now() / 1e3) && !session.revoked;
|
|
2456
|
+
}
|
|
2457
|
+
/** Return the parent account of a delegated session key. */
|
|
2458
|
+
async getSessionKeyOwner(sessionKey) {
|
|
2459
|
+
return this.askValidator.read.sessionKeyOwner([sessionKey]);
|
|
2460
|
+
}
|
|
2461
|
+
/** Return the parent key that delegated to subKey, or ZeroAddress if not delegated. */
|
|
2462
|
+
async getDelegatedBy(account, subKey) {
|
|
2463
|
+
return this.askValidator.read.delegatedBy([account, subKey]);
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
function packSecp256k1SessionSignature(account, sessionKey, signature) {
|
|
2467
|
+
const acc = account.startsWith("0x") ? account.slice(2) : account;
|
|
2468
|
+
const key = sessionKey.startsWith("0x") ? sessionKey.slice(2) : sessionKey;
|
|
2469
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
2470
|
+
if (acc.length !== 40) throw new Error("account must be 20 bytes (40 hex chars)");
|
|
2471
|
+
if (key.length !== 40) throw new Error("sessionKey must be 20 bytes (40 hex chars)");
|
|
2472
|
+
if (sig.length !== 130) throw new Error("signature must be 65 bytes (130 hex chars)");
|
|
2473
|
+
return `0x08${acc}${key}${sig}`;
|
|
2474
|
+
}
|
|
2475
|
+
function packP256SessionSignature(account, keyX, keyY, signature) {
|
|
2476
|
+
const acc = account.startsWith("0x") ? account.slice(2) : account;
|
|
2477
|
+
const x = keyX.startsWith("0x") ? keyX.slice(2) : keyX;
|
|
2478
|
+
const y = keyY.startsWith("0x") ? keyY.slice(2) : keyY;
|
|
2479
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
2480
|
+
if (acc.length !== 40) throw new Error("account must be 20 bytes (40 hex chars)");
|
|
2481
|
+
if (x.length !== 64) throw new Error("keyX must be 32 bytes (64 hex chars)");
|
|
2482
|
+
if (y.length !== 64) throw new Error("keyY must be 32 bytes (64 hex chars)");
|
|
2483
|
+
if (sig.length !== 128) throw new Error("P256 signature must be 64 bytes (128 hex chars, R||S)");
|
|
2484
|
+
return `0x08${acc}${x}${y}${sig}`;
|
|
2485
|
+
}
|
|
2486
|
+
var EXTENDED_GUARD_ABI = [
|
|
2487
|
+
...GLOBAL_GUARD_ABI,
|
|
2488
|
+
"function todaySpent() external view returns (uint256)",
|
|
2489
|
+
"function tokenTodaySpent(address token) external view returns (uint256)",
|
|
2490
|
+
// approvedAlgorithms removed from the guard in v0.17.2-beta.4 — now read from the account.
|
|
2491
|
+
"function tier1Limit() external view returns (uint256)",
|
|
2492
|
+
"function tier2Limit() external view returns (uint256)",
|
|
2493
|
+
"function minDailyLimit() external view returns (uint256)"
|
|
2494
|
+
];
|
|
2495
|
+
var GuardStateReader = class {
|
|
2496
|
+
provider;
|
|
2497
|
+
constructor(provider) {
|
|
2498
|
+
this.provider = provider;
|
|
2499
|
+
}
|
|
2500
|
+
accountContract(accountAddress) {
|
|
2501
|
+
return viem.getContract({
|
|
2502
|
+
address: accountAddress,
|
|
2503
|
+
abi: viem.parseAbi(AIRACCOUNT_ABI),
|
|
2504
|
+
client: this.provider
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
guardContract(guardAddress) {
|
|
2508
|
+
return viem.getContract({
|
|
2509
|
+
address: guardAddress,
|
|
2510
|
+
abi: viem.parseAbi(EXTENDED_GUARD_ABI),
|
|
2511
|
+
client: this.provider
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
/**
|
|
2515
|
+
* Read the full ETH guard state for an account.
|
|
2516
|
+
* Returns null if the account has no guard (dailyLimit=0).
|
|
2517
|
+
*/
|
|
2518
|
+
async getGuardState(accountAddress) {
|
|
2519
|
+
const account = this.accountContract(accountAddress).read;
|
|
2520
|
+
const guardAddress = await account.guard([]);
|
|
2521
|
+
if (guardAddress === viem.zeroAddress) return null;
|
|
2522
|
+
const guard = this.guardContract(guardAddress).read;
|
|
2523
|
+
const [dailyLimit, remaining, todaySpent, tier1Limit, tier2Limit, minDailyLimit] = await Promise.all([
|
|
2524
|
+
guard.dailyLimit([]),
|
|
2525
|
+
guard.remainingDailyAllowance([]),
|
|
2526
|
+
guard.todaySpent([]),
|
|
2527
|
+
guard.tier1Limit([]).catch(() => 0n),
|
|
2528
|
+
guard.tier2Limit([]).catch(() => 0n),
|
|
2529
|
+
guard.minDailyLimit([]).catch(() => 0n)
|
|
2530
|
+
]);
|
|
2531
|
+
return {
|
|
2532
|
+
dailyLimit: BigInt(dailyLimit),
|
|
2533
|
+
todaySpent: BigInt(todaySpent),
|
|
2534
|
+
remaining: BigInt(remaining),
|
|
2535
|
+
currentTier: resolveTierFromSpend(
|
|
2536
|
+
BigInt(todaySpent),
|
|
2537
|
+
BigInt(tier1Limit),
|
|
2538
|
+
BigInt(tier2Limit)
|
|
2539
|
+
),
|
|
2540
|
+
tier1Limit: BigInt(tier1Limit),
|
|
2541
|
+
tier2Limit: BigInt(tier2Limit),
|
|
2542
|
+
minDailyLimit: BigInt(minDailyLimit),
|
|
2543
|
+
guardAddress
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Read per-token guard state.
|
|
2548
|
+
* Returns null if the token is not configured on the guard.
|
|
2549
|
+
*/
|
|
2550
|
+
async getTokenGuardState(accountAddress, token) {
|
|
2551
|
+
const account = this.accountContract(accountAddress).read;
|
|
2552
|
+
const guardAddress = await account.guard([]);
|
|
2553
|
+
if (guardAddress === viem.zeroAddress) return null;
|
|
2554
|
+
const guard = this.guardContract(guardAddress).read;
|
|
2555
|
+
try {
|
|
2556
|
+
const todaySpent = await guard.tokenTodaySpent([token]);
|
|
2557
|
+
return {
|
|
2558
|
+
token,
|
|
2559
|
+
todaySpent: BigInt(todaySpent),
|
|
2560
|
+
dailyLimit: 0n,
|
|
2561
|
+
// token daily limit not directly exposed
|
|
2562
|
+
remaining: 0n,
|
|
2563
|
+
currentTier: 1,
|
|
2564
|
+
tier1Limit: 0n,
|
|
2565
|
+
tier2Limit: 0n
|
|
2566
|
+
};
|
|
2567
|
+
} catch {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Determine the minimum tier required to send a given ETH amount.
|
|
2573
|
+
* Useful for showing "this transfer needs 2 signatures" before submission.
|
|
2574
|
+
*/
|
|
2575
|
+
async requiredTierForAmount(accountAddress, amountWei) {
|
|
2576
|
+
const state = await this.getGuardState(accountAddress);
|
|
2577
|
+
if (!state) return 1;
|
|
2578
|
+
const projectedSpend = state.todaySpent + amountWei;
|
|
2579
|
+
return resolveTierFromSpend(projectedSpend, state.tier1Limit, state.tier2Limit);
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Check if a given algorithm ID is approved on the guard.
|
|
2583
|
+
*/
|
|
2584
|
+
async isAlgorithmApproved(accountAddress, algId) {
|
|
2585
|
+
const account = this.accountContract(accountAddress).read;
|
|
2586
|
+
return await account.approvedAlgorithms([BigInt(algId)]);
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
function resolveTierFromSpend(spent, tier1Limit, tier2Limit) {
|
|
2590
|
+
if (tier1Limit === 0n) return 1;
|
|
2591
|
+
if (spent < tier1Limit) return 1;
|
|
2592
|
+
if (tier2Limit === 0n || spent < tier2Limit) return 2;
|
|
2593
|
+
return 3;
|
|
2594
|
+
}
|
|
2595
|
+
var FACTORY_ABI = viem.parseAbi(AIRACCOUNT_FACTORY_ABI);
|
|
2596
|
+
function computeOapdSalt(owner, dappId) {
|
|
2597
|
+
const packed = chunkXQROKLZI_cjs.solidityPacked(["address", "string"], [owner, dappId]);
|
|
2598
|
+
return BigInt(chunkXQROKLZI_cjs.keccak256(packed));
|
|
2599
|
+
}
|
|
2600
|
+
async function getOapdAddress(provider, config) {
|
|
2601
|
+
const factoryAddress = config.factoryAddress ?? AIRACCOUNT_ADDRESSES.sepolia.factory;
|
|
2602
|
+
const salt = computeOapdSalt(config.owner, config.dappId);
|
|
2603
|
+
return provider.readContract({
|
|
2604
|
+
address: factoryAddress,
|
|
2605
|
+
abi: FACTORY_ABI,
|
|
2606
|
+
functionName: "getAddress",
|
|
2607
|
+
args: [config.owner, salt, config.initConfig]
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
async function getOapdAddressWithChainId(provider, config) {
|
|
2611
|
+
const factoryAddress = config.factoryAddress ?? AIRACCOUNT_ADDRESSES.sepolia.factory;
|
|
2612
|
+
const salt = computeOapdSalt(config.owner, config.dappId);
|
|
2613
|
+
const result = await provider.readContract({
|
|
2614
|
+
address: factoryAddress,
|
|
2615
|
+
abi: FACTORY_ABI,
|
|
2616
|
+
functionName: "getAddressWithChainId",
|
|
2617
|
+
args: [config.owner, salt, config.initConfig]
|
|
2618
|
+
});
|
|
2619
|
+
return { address: result[0], chainQualified: result[1] };
|
|
2620
|
+
}
|
|
2621
|
+
async function isOapdDeployed(provider, config) {
|
|
2622
|
+
const address = await getOapdAddress(provider, config);
|
|
2623
|
+
const code = await provider.getCode({ address });
|
|
2624
|
+
return code !== void 0 && code !== "0x";
|
|
2625
|
+
}
|
|
2626
|
+
var ALG_NAMES = {
|
|
2627
|
+
[chunkXQROKLZI_cjs.ALG_BLS]: "BLS (0x01)",
|
|
2628
|
+
[chunkXQROKLZI_cjs.ALG_ECDSA]: "ECDSA (0x02)",
|
|
2629
|
+
[chunkXQROKLZI_cjs.ALG_P256]: "P256 (0x03)",
|
|
2630
|
+
[chunkXQROKLZI_cjs.ALG_CUMULATIVE_T2]: "Cumulative T2 (0x04)",
|
|
2631
|
+
[chunkXQROKLZI_cjs.ALG_CUMULATIVE_T3]: "Cumulative T3 (0x05)"
|
|
2632
|
+
};
|
|
2633
|
+
var GuardChecker = class {
|
|
2634
|
+
constructor(ethereum, logger) {
|
|
2635
|
+
this.ethereum = ethereum;
|
|
2636
|
+
this.logger = logger ?? new ConsoleLogger("[GuardChecker]");
|
|
2637
|
+
}
|
|
2638
|
+
logger;
|
|
2639
|
+
/**
|
|
2640
|
+
* Fetch tier limits from an AirAccount contract.
|
|
2641
|
+
*/
|
|
2642
|
+
async fetchTierConfig(accountAddress) {
|
|
2643
|
+
const account = this.ethereum.getAccountContract(accountAddress);
|
|
2644
|
+
return readAccountTierLimits(account);
|
|
2645
|
+
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Fetch guard status from the account's GlobalGuard.
|
|
2648
|
+
*/
|
|
2649
|
+
async fetchGuardStatus(accountAddress) {
|
|
2650
|
+
const account = this.ethereum.getAccountContract(accountAddress);
|
|
2651
|
+
const guardAddress = await readAccountGuardAddress(account);
|
|
2652
|
+
if (guardAddress === viem.zeroAddress) {
|
|
2653
|
+
return {
|
|
2654
|
+
hasGuard: false,
|
|
2655
|
+
guardAddress: viem.zeroAddress,
|
|
2656
|
+
dailyLimit: 0n,
|
|
2657
|
+
dailyRemaining: 0n
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
const guard = viem.getContract({
|
|
2661
|
+
address: guardAddress,
|
|
2662
|
+
abi: viem.parseAbi(GLOBAL_GUARD_ABI),
|
|
2663
|
+
client: this.ethereum.getProvider()
|
|
2664
|
+
});
|
|
2665
|
+
const { dailyLimit, dailyRemaining } = await readGuardDailyAllowance(guard);
|
|
2666
|
+
return {
|
|
2667
|
+
hasGuard: true,
|
|
2668
|
+
guardAddress,
|
|
2669
|
+
dailyLimit,
|
|
2670
|
+
dailyRemaining
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Pre-check a transaction: determine tier, check guard limits and algorithm approval.
|
|
2675
|
+
* Returns errors array (empty = OK to proceed).
|
|
2676
|
+
*/
|
|
2677
|
+
async preCheck(accountAddress, value) {
|
|
2678
|
+
const errors = [];
|
|
2679
|
+
const tierConfig = await this.fetchTierConfig(accountAddress);
|
|
2680
|
+
const tier = chunkXQROKLZI_cjs.resolveTier(value, tierConfig);
|
|
2681
|
+
const algId = chunkXQROKLZI_cjs.algIdForTier(tier);
|
|
2682
|
+
const guard = await this.fetchGuardStatus(accountAddress);
|
|
2683
|
+
if (!guard.hasGuard) {
|
|
2684
|
+
return { ok: true, errors: [], tier, algId };
|
|
2685
|
+
}
|
|
2686
|
+
if (guard.dailyLimit > 0n && value > guard.dailyRemaining) {
|
|
2687
|
+
errors.push(
|
|
2688
|
+
`Daily limit exceeded: requesting ${value} wei but only ${guard.dailyRemaining} remaining (limit: ${guard.dailyLimit})`
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
const accountContract = this.ethereum.getAccountContract(accountAddress);
|
|
2692
|
+
const isApproved = await readAlgorithmApproved(accountContract, algId);
|
|
2693
|
+
if (!isApproved) {
|
|
2694
|
+
errors.push(
|
|
2695
|
+
`Algorithm ${ALG_NAMES[algId] ?? `0x${algId.toString(16)}`} is not approved by the account`
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
if (errors.length > 0) {
|
|
2699
|
+
this.logger.warn(`Pre-check failed for ${accountAddress}: ${errors.join("; ")}`);
|
|
2700
|
+
}
|
|
2701
|
+
return { ok: errors.length === 0, errors, tier, algId };
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
var FORCE_EXIT_ABI = [
|
|
2705
|
+
// ERC-7579 module lifecycle
|
|
2706
|
+
"function onInstall(bytes calldata data) external",
|
|
2707
|
+
"function onUninstall(bytes calldata data) external",
|
|
2708
|
+
"function isInitialized(address smartAccount) external view returns (bool)",
|
|
2709
|
+
// Proposal lifecycle
|
|
2710
|
+
"function proposeForceExit(address target, uint256 value, bytes calldata data) external",
|
|
2711
|
+
"function approveForceExit(address account, bytes calldata guardianSig) external",
|
|
2712
|
+
"function executeForceExit(address account) external",
|
|
2713
|
+
"function cancelForceExit(address account) external",
|
|
2714
|
+
// State readers
|
|
2715
|
+
"function getPendingExit(address account) external view returns (address target, uint256 value, bytes memory data, uint256 proposedAt, uint256 approvalBitmap, address[3] memory guardians)",
|
|
2716
|
+
"function accountL2Type(address account) external view returns (uint8)",
|
|
2717
|
+
// Constants
|
|
2718
|
+
"function APPROVAL_THRESHOLD() external view returns (uint8)",
|
|
2719
|
+
"function MODULE_VERSION() external view returns (string)",
|
|
2720
|
+
// Errors (for decoding reverts)
|
|
2721
|
+
"error AlreadyApproved()",
|
|
2722
|
+
"error AlreadyProposed()",
|
|
2723
|
+
"error ForceExitCallFailed()",
|
|
2724
|
+
"error IncompatibleAccount()",
|
|
2725
|
+
"error InvalidGuardianSig()",
|
|
2726
|
+
"error NoProposal()",
|
|
2727
|
+
"error NotEnoughApprovals()",
|
|
2728
|
+
"error NotInstalled()",
|
|
2729
|
+
"error NotOwner()",
|
|
2730
|
+
"error SignerNoLongerGuardian()",
|
|
2731
|
+
"error UnsupportedL2Type()"
|
|
2732
|
+
];
|
|
2733
|
+
var FORCE_EXIT_PARSED_ABI = viem.parseAbi(FORCE_EXIT_ABI);
|
|
2734
|
+
var L2_TYPE = {
|
|
2735
|
+
OPTIMISM: 1,
|
|
2736
|
+
ARBITRUM: 2
|
|
2737
|
+
};
|
|
2738
|
+
var ForceExitService = class {
|
|
2739
|
+
constructor(moduleAddress, client) {
|
|
2740
|
+
this.moduleAddress = moduleAddress;
|
|
2741
|
+
this.contract = viem.getContract({
|
|
2742
|
+
address: moduleAddress,
|
|
2743
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2744
|
+
// viem inspects the client's actions at runtime to expose read/write; the
|
|
2745
|
+
// cast only satisfies the static union — a wallet client still yields writes.
|
|
2746
|
+
client
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
contract;
|
|
2750
|
+
// ── On-chain reads ──────────────────────────────────────────────
|
|
2751
|
+
async isInitialized(smartAccount) {
|
|
2752
|
+
return await this.contract.read.isInitialized([smartAccount]);
|
|
2753
|
+
}
|
|
2754
|
+
async getPendingExit(account) {
|
|
2755
|
+
const [target, value, data, proposedAt, approvalBitmap, guardians] = await this.contract.read.getPendingExit([account]);
|
|
2756
|
+
return {
|
|
2757
|
+
target,
|
|
2758
|
+
value: BigInt(value),
|
|
2759
|
+
data,
|
|
2760
|
+
proposedAt: BigInt(proposedAt),
|
|
2761
|
+
approvalBitmap: BigInt(approvalBitmap),
|
|
2762
|
+
guardians
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
async getAccountL2Type(account) {
|
|
2766
|
+
return Number(await this.contract.read.accountL2Type([account]));
|
|
2767
|
+
}
|
|
2768
|
+
async getApprovalThreshold() {
|
|
2769
|
+
return Number(await this.contract.read.APPROVAL_THRESHOLD([]));
|
|
2770
|
+
}
|
|
2771
|
+
async getModuleVersion() {
|
|
2772
|
+
return await this.contract.read.MODULE_VERSION([]);
|
|
2773
|
+
}
|
|
2774
|
+
// ── Calldata encoders (for UserOp or direct tx submission) ─────
|
|
2775
|
+
/**
|
|
2776
|
+
* Encode onInstall calldata for installModule() call on the smart account.
|
|
2777
|
+
* Must be submitted by the account owner, with moduleTypeId=2 (EXECUTOR).
|
|
2778
|
+
*
|
|
2779
|
+
* @param l2Type - L2_TYPE.OPTIMISM (1) or L2_TYPE.ARBITRUM (2)
|
|
2780
|
+
* @example
|
|
2781
|
+
* const calldata = forceExit.encodeOnInstall(L2_TYPE.OPTIMISM);
|
|
2782
|
+
* // account.installModule(2, forceExitModuleAddress, calldata)
|
|
2783
|
+
*/
|
|
2784
|
+
encodeOnInstall(l2Type) {
|
|
2785
|
+
return viem.encodeFunctionData({
|
|
2786
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2787
|
+
functionName: "onInstall",
|
|
2788
|
+
args: [chunkXQROKLZI_cjs.encodeAbiParams(["uint8"], [l2Type])]
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
encodeOnUninstall() {
|
|
2792
|
+
return viem.encodeFunctionData({
|
|
2793
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2794
|
+
functionName: "onUninstall",
|
|
2795
|
+
args: ["0x"]
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Encode calldata for proposeForceExit — the exit payload to bridge out of L2.
|
|
2800
|
+
* `target` is the L2→L1 bridge contract; `data` is the bridge call payload.
|
|
2801
|
+
*/
|
|
2802
|
+
encodeProposeForceExit(target, value, data) {
|
|
2803
|
+
return viem.encodeFunctionData({
|
|
2804
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2805
|
+
functionName: "proposeForceExit",
|
|
2806
|
+
args: [target, value, data]
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Encode calldata for approveForceExit — guardian signs off on the pending proposal.
|
|
2811
|
+
* `guardianSig` must be an EIP-191 personal_sign over the proposal hash.
|
|
2812
|
+
*/
|
|
2813
|
+
encodeApproveForceExit(account, guardianSig) {
|
|
2814
|
+
return viem.encodeFunctionData({
|
|
2815
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2816
|
+
functionName: "approveForceExit",
|
|
2817
|
+
args: [account, guardianSig]
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
encodeExecuteForceExit(account) {
|
|
2821
|
+
return viem.encodeFunctionData({
|
|
2822
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2823
|
+
functionName: "executeForceExit",
|
|
2824
|
+
args: [account]
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
encodeCancelForceExit(account) {
|
|
2828
|
+
return viem.encodeFunctionData({
|
|
2829
|
+
abi: FORCE_EXIT_PARSED_ABI,
|
|
2830
|
+
functionName: "cancelForceExit",
|
|
2831
|
+
args: [account]
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
// ── Convenience: send transactions (requires a WalletClient) ──────────
|
|
2835
|
+
async proposeForceExit(target, value, data) {
|
|
2836
|
+
return await this.contract.write.proposeForceExit([
|
|
2837
|
+
target,
|
|
2838
|
+
value,
|
|
2839
|
+
data
|
|
2840
|
+
]);
|
|
2841
|
+
}
|
|
2842
|
+
async approveForceExit(account, guardianSig) {
|
|
2843
|
+
return await this.contract.write.approveForceExit([
|
|
2844
|
+
account,
|
|
2845
|
+
guardianSig
|
|
2846
|
+
]);
|
|
2847
|
+
}
|
|
2848
|
+
async executeForceExit(account) {
|
|
2849
|
+
return await this.contract.write.executeForceExit([account]);
|
|
2850
|
+
}
|
|
2851
|
+
async cancelForceExit(account) {
|
|
2852
|
+
return await this.contract.write.cancelForceExit([account]);
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
var AIRACCOUNT_ABI_PARSED2 = viem.parseAbi(AIRACCOUNT_ABI);
|
|
2856
|
+
var RECOVERY_THRESHOLD = 2;
|
|
2857
|
+
var MAX_GUARDIANS = 3;
|
|
2858
|
+
var RECOVERY_TIMELOCK_SECONDS = 2n * 24n * 60n * 60n;
|
|
2859
|
+
var GUARDIAN_SIG_VERSION = 4;
|
|
2860
|
+
function popcount(value) {
|
|
2861
|
+
let v = value;
|
|
2862
|
+
let count = 0;
|
|
2863
|
+
while (v > 0n) {
|
|
2864
|
+
count += Number(v & 1n);
|
|
2865
|
+
v >>= 1n;
|
|
2866
|
+
}
|
|
2867
|
+
return count;
|
|
2868
|
+
}
|
|
2869
|
+
var RecoveryService = class {
|
|
2870
|
+
/**
|
|
2871
|
+
* @param client viem read client (was `ethers.Provider | ethers.Signer`). Only
|
|
2872
|
+
* on-chain reads are performed here; calldata encoders are pure and never
|
|
2873
|
+
* touch the client.
|
|
2874
|
+
*/
|
|
2875
|
+
constructor(client) {
|
|
2876
|
+
this.client = client;
|
|
2877
|
+
}
|
|
2878
|
+
// ── Calldata encoders (submit TO the account address) ─────────────
|
|
2879
|
+
/**
|
|
2880
|
+
* Encode `addGuardian(guardian)` calldata. **Owner only.**
|
|
2881
|
+
* Registers a recovery guardian; reverts once 3 guardians are set, or if the
|
|
2882
|
+
* guardian is `address(0)`, the owner, or already registered.
|
|
2883
|
+
*/
|
|
2884
|
+
encodeAddGuardian(guardian) {
|
|
2885
|
+
return viem.encodeFunctionData({
|
|
2886
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
2887
|
+
functionName: "addGuardian",
|
|
2888
|
+
args: [guardian]
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* Encode `removeGuardian(index, guardianSigs)` calldata. **Owner only**, and
|
|
2893
|
+
* requires >= {@link RECOVERY_THRESHOLD} guardian signatures over the removal
|
|
2894
|
+
* hash. Cannot remove while a recovery is active, nor drop below 2 guardians.
|
|
2895
|
+
*
|
|
2896
|
+
* @param index Guardian slot to remove (0-indexed).
|
|
2897
|
+
* @param guardianSigs EIP-191 guardian signatures over the removal hash.
|
|
2898
|
+
*/
|
|
2899
|
+
encodeRemoveGuardian(index, guardianSigs) {
|
|
2900
|
+
return viem.encodeFunctionData({
|
|
2901
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
2902
|
+
functionName: "removeGuardian",
|
|
2903
|
+
args: [index, guardianSigs]
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
/**
|
|
2907
|
+
* Build the RAW (un-prefixed) challenge hash that each guardian must sign to
|
|
2908
|
+
* authorize `removeGuardian(index, ...)` / `removeGuardianWithMixedSigs(...)`.
|
|
2909
|
+
*
|
|
2910
|
+
* ## v0.20.0 breaking change (#120 final-review [HIGH], spec §6.4)
|
|
2911
|
+
* The signed `opData` changed from `abi.encode(nonce, guardianToRemove)` to
|
|
2912
|
+
* `abi.encode(nonce, index, guardianToRemove, p256X, p256Y)` — it now binds the
|
|
2913
|
+
* SLOT INDEX and the slot's P-256 key. Because P-256 guardians all share the
|
|
2914
|
+
* sentinel address, the old 2-field payload was identical for every P-256 slot,
|
|
2915
|
+
* so a signature collected to remove slot A could be replayed to remove slot B
|
|
2916
|
+
* (or survive a key rotation). The new 5-field payload affects EVERY removal,
|
|
2917
|
+
* including the plain ECDSA path: for an ECDSA slot `p256X`/`p256Y` are both
|
|
2918
|
+
* `bytes32(0)`, but the payload STRUCTURE (extra `index` + two key words) still
|
|
2919
|
+
* changed, so the ECDSA `removeGuardian` signing payload MUST use this encoding.
|
|
2920
|
+
*
|
|
2921
|
+
* Hash construction (matches `AAStarAirAccountBase._guardianOpHash`):
|
|
2922
|
+
* ```
|
|
2923
|
+
* opData = abi.encode(uint256 nonce, uint8 index, address guardianToRemove,
|
|
2924
|
+
* bytes32 p256X, bytes32 p256Y)
|
|
2925
|
+
* challenge = keccak256(abi.encode(uint8 GUARDIAN_SIG_VERSION, uint256 chainId,
|
|
2926
|
+
* address account, "REMOVE_GUARDIAN", bytes opData))
|
|
2927
|
+
* ```
|
|
2928
|
+
* The contract additionally applies `toEthSignedMessageHash()` before
|
|
2929
|
+
* `ecrecover`, so this returns the RAW inner hash and each guardian signs it via
|
|
2930
|
+
* `personal_sign` / `signMessage({ raw: hash })` (which adds the EIP-191 prefix).
|
|
2931
|
+
* Do NOT pre-apply the prefix here (mirrors `buildGuardianAcceptanceHash`).
|
|
2932
|
+
*
|
|
2933
|
+
* @param account The AirAccount address whose guardian is being removed.
|
|
2934
|
+
* @param chainId EVM chain id (bound into the challenge).
|
|
2935
|
+
* @param removalNonce Current `_guardianRemovalNonce` — there is no on-chain
|
|
2936
|
+
* getter (internal storage slot 15), so the caller tracks
|
|
2937
|
+
* it (starts at 0, increments once per successful removal).
|
|
2938
|
+
* @param index Guardian slot being removed (0-indexed, < guardianCount).
|
|
2939
|
+
* @param guardianToRemove Address stored in that slot (a P-256 slot stores
|
|
2940
|
+
* {@link P256_GUARDIAN_SENTINEL}; an ECDSA slot stores the EOA).
|
|
2941
|
+
* @param p256X Slot's P-256 x coordinate; `bytes32(0)` for an ECDSA slot (default).
|
|
2942
|
+
* @param p256Y Slot's P-256 y coordinate; `bytes32(0)` for an ECDSA slot (default).
|
|
2943
|
+
* @returns raw hex keccak256 challenge — guardians sign it with `personal_sign`.
|
|
2944
|
+
*/
|
|
2945
|
+
buildRemoveGuardianHash(args) {
|
|
2946
|
+
const ZERO323 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
2947
|
+
const opData = viem.encodeAbiParameters(
|
|
2948
|
+
[
|
|
2949
|
+
{ type: "uint256" },
|
|
2950
|
+
// _guardianRemovalNonce
|
|
2951
|
+
{ type: "uint8" },
|
|
2952
|
+
// index
|
|
2953
|
+
{ type: "address" },
|
|
2954
|
+
// guardianToRemove
|
|
2955
|
+
{ type: "bytes32" },
|
|
2956
|
+
// p256X (0 for ECDSA slot)
|
|
2957
|
+
{ type: "bytes32" }
|
|
2958
|
+
// p256Y (0 for ECDSA slot)
|
|
2959
|
+
],
|
|
2960
|
+
[
|
|
2961
|
+
args.removalNonce,
|
|
2962
|
+
args.index,
|
|
2963
|
+
args.guardianToRemove,
|
|
2964
|
+
args.p256X ?? ZERO323,
|
|
2965
|
+
args.p256Y ?? ZERO323
|
|
2966
|
+
]
|
|
2967
|
+
);
|
|
2968
|
+
return viem.keccak256(
|
|
2969
|
+
viem.encodeAbiParameters(
|
|
2970
|
+
[
|
|
2971
|
+
{ type: "uint8" },
|
|
2972
|
+
// GUARDIAN_SIG_VERSION
|
|
2973
|
+
{ type: "uint256" },
|
|
2974
|
+
// chainId
|
|
2975
|
+
{ type: "address" },
|
|
2976
|
+
// address(this) — the account
|
|
2977
|
+
{ type: "string" },
|
|
2978
|
+
// opLabel
|
|
2979
|
+
{ type: "bytes" }
|
|
2980
|
+
// opData
|
|
2981
|
+
],
|
|
2982
|
+
[
|
|
2983
|
+
GUARDIAN_SIG_VERSION,
|
|
2984
|
+
BigInt(args.chainId),
|
|
2985
|
+
args.account,
|
|
2986
|
+
"REMOVE_GUARDIAN",
|
|
2987
|
+
opData
|
|
2988
|
+
]
|
|
2989
|
+
)
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
/**
|
|
2993
|
+
* Encode `proposeRecovery(newOwner)` calldata. **Any guardian** may call.
|
|
2994
|
+
* Starts the {@link RECOVERY_TIMELOCK_SECONDS} timelock and records the
|
|
2995
|
+
* proposer's approval (1 of {@link RECOVERY_THRESHOLD}).
|
|
2996
|
+
*/
|
|
2997
|
+
encodeProposeRecovery(newOwner) {
|
|
2998
|
+
return viem.encodeFunctionData({
|
|
2999
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3000
|
+
functionName: "proposeRecovery",
|
|
3001
|
+
args: [newOwner]
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Encode `approveRecovery()` calldata. **Another guardian** approves the
|
|
3006
|
+
* active proposal, setting its bit in `approvalBitmap`.
|
|
3007
|
+
*/
|
|
3008
|
+
encodeApproveRecovery() {
|
|
3009
|
+
return viem.encodeFunctionData({
|
|
3010
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3011
|
+
functionName: "approveRecovery",
|
|
3012
|
+
args: []
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Encode `cancelRecovery()` calldata. **Guardians only** — each call is one
|
|
3017
|
+
* vote; recovery is dropped once {@link RECOVERY_THRESHOLD} cancel votes are
|
|
3018
|
+
* reached. The owner cannot cancel.
|
|
3019
|
+
*/
|
|
3020
|
+
encodeCancelRecovery() {
|
|
3021
|
+
return viem.encodeFunctionData({
|
|
3022
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3023
|
+
functionName: "cancelRecovery",
|
|
3024
|
+
args: []
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
/**
|
|
3028
|
+
* Encode `executeRecovery()` calldata. **Anyone** may call, but it only
|
|
3029
|
+
* succeeds once the timelock has elapsed and the approval threshold is met.
|
|
3030
|
+
* Rotates the account owner to the proposed `newOwner`.
|
|
3031
|
+
*/
|
|
3032
|
+
encodeExecuteRecovery() {
|
|
3033
|
+
return viem.encodeFunctionData({
|
|
3034
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3035
|
+
functionName: "executeRecovery",
|
|
3036
|
+
args: []
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
// ── On-chain reads (against the account address) ──────────────────
|
|
3040
|
+
/**
|
|
3041
|
+
* Read and decode the account's `activeRecovery()` struct.
|
|
3042
|
+
* Returns derived `approvalCount`, `cancellationCount`, `executeAfter`, and
|
|
3043
|
+
* `isActive` alongside the raw fields.
|
|
3044
|
+
*
|
|
3045
|
+
* @param account The AirAccount address to query.
|
|
3046
|
+
*/
|
|
3047
|
+
async getActiveRecovery(account) {
|
|
3048
|
+
const [newOwner, proposedAt, approvalBitmap, cancellationBitmap] = await this.client.readContract({
|
|
3049
|
+
address: account,
|
|
3050
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3051
|
+
functionName: "activeRecovery"
|
|
3052
|
+
});
|
|
3053
|
+
const proposedAtBn = BigInt(proposedAt);
|
|
3054
|
+
const approvalBitmapBn = BigInt(approvalBitmap);
|
|
3055
|
+
const cancellationBitmapBn = BigInt(cancellationBitmap);
|
|
3056
|
+
return {
|
|
3057
|
+
newOwner,
|
|
3058
|
+
proposedAt: proposedAtBn,
|
|
3059
|
+
approvalBitmap: approvalBitmapBn,
|
|
3060
|
+
cancellationBitmap: cancellationBitmapBn,
|
|
3061
|
+
approvalCount: popcount(approvalBitmapBn),
|
|
3062
|
+
cancellationCount: popcount(cancellationBitmapBn),
|
|
3063
|
+
executeAfter: proposedAtBn + RECOVERY_TIMELOCK_SECONDS,
|
|
3064
|
+
isActive: newOwner.toLowerCase() !== viem.zeroAddress
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Read the number of registered guardians via `guardianCount()`.
|
|
3069
|
+
*
|
|
3070
|
+
* @param account The AirAccount address to query.
|
|
3071
|
+
*/
|
|
3072
|
+
async getGuardianCount(account) {
|
|
3073
|
+
const count = await this.client.readContract({
|
|
3074
|
+
address: account,
|
|
3075
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3076
|
+
functionName: "guardianCount"
|
|
3077
|
+
});
|
|
3078
|
+
return Number(count);
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Read the full guardian address list.
|
|
3082
|
+
*
|
|
3083
|
+
* The V7 account exposes positional `guardians(uint256 i)` (3 packed slots) plus
|
|
3084
|
+
* `guardianCount()` — there is no single `getGuardians()` getter on the account
|
|
3085
|
+
* (that exists only on `AirAccountDelegate`, the EIP-7702 path). This reads slots
|
|
3086
|
+
* `0..guardianCount-1` and returns the non-zero guardian addresses.
|
|
3087
|
+
*
|
|
3088
|
+
* @param account The AirAccount address to query.
|
|
3089
|
+
*/
|
|
3090
|
+
async getGuardians(account) {
|
|
3091
|
+
const count = Number(
|
|
3092
|
+
await this.client.readContract({
|
|
3093
|
+
address: account,
|
|
3094
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3095
|
+
functionName: "guardianCount"
|
|
3096
|
+
})
|
|
3097
|
+
);
|
|
3098
|
+
const guardians = [];
|
|
3099
|
+
for (let i = 0; i < count; i++) {
|
|
3100
|
+
const g = await this.client.readContract({
|
|
3101
|
+
address: account,
|
|
3102
|
+
abi: AIRACCOUNT_ABI_PARSED2,
|
|
3103
|
+
functionName: "guardians",
|
|
3104
|
+
args: [BigInt(i)]
|
|
3105
|
+
});
|
|
3106
|
+
if (g.toLowerCase() !== viem.zeroAddress) guardians.push(g);
|
|
3107
|
+
}
|
|
3108
|
+
return guardians;
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
var DELEGATE_ABI = [
|
|
3112
|
+
"function initialize(address guardian1, bytes calldata g1Sig, address guardian2, bytes calldata g2Sig, uint256 dailyLimit) external",
|
|
3113
|
+
"function owner() external view returns (address)",
|
|
3114
|
+
"function isInitialized() external view returns (bool)",
|
|
3115
|
+
"function entryPoint() external view returns (address)",
|
|
3116
|
+
"function getGuardians() external view returns (address[3] memory)",
|
|
3117
|
+
"function getDeposit() external view returns (uint256)",
|
|
3118
|
+
"function execute(address dest, uint256 value, bytes calldata data) external",
|
|
3119
|
+
"function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata data) external",
|
|
3120
|
+
"function validateUserOp((address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature) calldata userOp, bytes32 userOpHash, uint256 missingFunds) external returns (uint256)",
|
|
3121
|
+
"function initiateRescue(address rescueTo) external",
|
|
3122
|
+
"function approveRescue() external",
|
|
3123
|
+
"function cancelRescue() external",
|
|
3124
|
+
"function executeRescue() external",
|
|
3125
|
+
"function addDeposit() external payable",
|
|
3126
|
+
"function withdrawDepositTo(address to, uint256 amount) external",
|
|
3127
|
+
"error AlreadyInitialized()",
|
|
3128
|
+
"error NotInitialized()",
|
|
3129
|
+
"error InvalidAddress()",
|
|
3130
|
+
"error InvalidGuardianSignature()"
|
|
3131
|
+
];
|
|
3132
|
+
var AIR_ACCOUNT_DELEGATE_ADDRESS = "0x8603AAF6C3f07fdae810B323c95a198D796EC52E";
|
|
3133
|
+
var EIP7702DelegateService = class {
|
|
3134
|
+
constructor(delegateAddress = AIR_ACCOUNT_DELEGATE_ADDRESS, client) {
|
|
3135
|
+
this.delegateAddress = delegateAddress;
|
|
3136
|
+
this.abi = viem.parseAbi(DELEGATE_ABI);
|
|
3137
|
+
this.client = client;
|
|
3138
|
+
}
|
|
3139
|
+
/** Parsed ABI (loose viem `Abi` shape) used for encoding calldata and on-chain reads. */
|
|
3140
|
+
abi;
|
|
3141
|
+
/** Optional viem read client (was `ethers.Provider | ethers.Signer`). Required only for on-chain reads. */
|
|
3142
|
+
client;
|
|
3143
|
+
// ── Calldata encoders ─────────────────────────────────────────
|
|
3144
|
+
/**
|
|
3145
|
+
* Encode initialize() calldata for the first post-delegation UserOp.
|
|
3146
|
+
* Must be the callData of a UserOp sent immediately after the SET_CODE delegation activates.
|
|
3147
|
+
* Guardian acceptance sigs follow the same EIP-712 scheme as AirAccountV7 createAccount.
|
|
3148
|
+
*/
|
|
3149
|
+
encodeInitialize(params) {
|
|
3150
|
+
return viem.encodeFunctionData({
|
|
3151
|
+
abi: this.abi,
|
|
3152
|
+
functionName: "initialize",
|
|
3153
|
+
args: [
|
|
3154
|
+
params.guardian1,
|
|
3155
|
+
params.guardian1Sig,
|
|
3156
|
+
params.guardian2,
|
|
3157
|
+
params.guardian2Sig,
|
|
3158
|
+
params.dailyLimit
|
|
3159
|
+
]
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
encodeExecute(dest, value, data) {
|
|
3163
|
+
return viem.encodeFunctionData({
|
|
3164
|
+
abi: this.abi,
|
|
3165
|
+
functionName: "execute",
|
|
3166
|
+
args: [dest, value, data]
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
encodeExecuteBatch(dests, values, datas) {
|
|
3170
|
+
return viem.encodeFunctionData({
|
|
3171
|
+
abi: this.abi,
|
|
3172
|
+
functionName: "executeBatch",
|
|
3173
|
+
args: [dests, values, datas]
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
// ── EIP-7702 authorization hash construction ──────────────────
|
|
3177
|
+
/**
|
|
3178
|
+
* Compute the EIP-7702 SET_CODE authorization hash that the EOA must sign.
|
|
3179
|
+
*
|
|
3180
|
+
* Hash = keccak256(0x05 || RLP([chainId, address, nonce]))
|
|
3181
|
+
*
|
|
3182
|
+
* This is the hash the private key signs to delegate code execution to
|
|
3183
|
+
* AirAccountDelegate. Use this hash with your KMS sign-hash endpoint or
|
|
3184
|
+
* local viem account.
|
|
3185
|
+
*
|
|
3186
|
+
* @param chainId - Target chain ID (11155111 for Sepolia)
|
|
3187
|
+
* @param nonce - EOA's current transaction nonce
|
|
3188
|
+
* @returns 32-byte hash (0x-prefixed) ready for signing
|
|
3189
|
+
*/
|
|
3190
|
+
buildAuthorizationHash(chainId, nonce) {
|
|
3191
|
+
return buildAuthorizationHash(chainId, nonce, this.delegateAddress);
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Build the full EIP-7702 authorization object for relay submission.
|
|
3195
|
+
* The caller must sign `buildAuthorizationHash()` externally and pass the result here.
|
|
3196
|
+
*
|
|
3197
|
+
* @param chainId - Target chain ID
|
|
3198
|
+
* @param nonce - EOA's current nonce
|
|
3199
|
+
* @param signature - 65-byte ECDSA signature (R||S||V) over the authorization hash
|
|
3200
|
+
*/
|
|
3201
|
+
buildAuthorization(chainId, nonce, signature) {
|
|
3202
|
+
return {
|
|
3203
|
+
chainId,
|
|
3204
|
+
address: this.delegateAddress,
|
|
3205
|
+
nonce,
|
|
3206
|
+
signature
|
|
3207
|
+
};
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Verify that a signature is a valid EIP-7702 authorization for the given EOA address.
|
|
3211
|
+
* Recovers the signer from the authorization hash and checks it matches `eoa`.
|
|
3212
|
+
*
|
|
3213
|
+
* NOTE: now async — viem's `recoverAddress` is asynchronous (ethers' was sync).
|
|
3214
|
+
*/
|
|
3215
|
+
verifyAuthorization(eoa, chainId, nonce, signature) {
|
|
3216
|
+
return verifyAuthorization(
|
|
3217
|
+
eoa,
|
|
3218
|
+
chainId,
|
|
3219
|
+
nonce,
|
|
3220
|
+
signature,
|
|
3221
|
+
this.delegateAddress
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
// ── On-chain reads (requires provider) ───────────────────────
|
|
3225
|
+
async isInitialized(eoa) {
|
|
3226
|
+
if (!this.client) throw new Error("EIP7702DelegateService: provider required for on-chain reads");
|
|
3227
|
+
return await this.client.readContract({
|
|
3228
|
+
address: eoa,
|
|
3229
|
+
abi: this.abi,
|
|
3230
|
+
functionName: "isInitialized"
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
async getOwner(eoa) {
|
|
3234
|
+
if (!this.client) throw new Error("EIP7702DelegateService: provider required for on-chain reads");
|
|
3235
|
+
return await this.client.readContract({
|
|
3236
|
+
address: eoa,
|
|
3237
|
+
abi: this.abi,
|
|
3238
|
+
functionName: "owner"
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
async getGuardians(eoa) {
|
|
3242
|
+
if (!this.client) throw new Error("EIP7702DelegateService: provider required for on-chain reads");
|
|
3243
|
+
return await this.client.readContract({
|
|
3244
|
+
address: eoa,
|
|
3245
|
+
abi: this.abi,
|
|
3246
|
+
functionName: "getGuardians"
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
};
|
|
3250
|
+
var WEIGHTED_SIGNATURE_ABI = [
|
|
3251
|
+
// Direct owner set (first-time / strengthening only)
|
|
3252
|
+
"function setWeightConfig((uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold) config) external",
|
|
3253
|
+
// Guardian-governed change flow (required for any weakening)
|
|
3254
|
+
"function proposeWeightChange((uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold) proposed) external",
|
|
3255
|
+
"function approveWeightChange() external",
|
|
3256
|
+
"function cancelWeightChange() external",
|
|
3257
|
+
"function executeWeightChange() external",
|
|
3258
|
+
// State readers
|
|
3259
|
+
"function weightConfig() external view returns (uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold)",
|
|
3260
|
+
"function pendingWeightChange() external view returns ((uint8 passkeyWeight, uint8 ecdsaWeight, uint8 blsWeight, uint8 guardian0Weight, uint8 guardian1Weight, uint8 guardian2Weight, uint8 _padding, uint8 tier1Threshold, uint8 tier2Threshold, uint8 tier3Threshold) proposed, uint256 proposedAt, uint256 approvalBitmap)",
|
|
3261
|
+
// Errors (for decoding reverts)
|
|
3262
|
+
"error InsecureWeightConfig()",
|
|
3263
|
+
"error WeakeningRequiresProposal()",
|
|
3264
|
+
"error WeightChangePending()",
|
|
3265
|
+
"error NoWeightChangeProposal()",
|
|
3266
|
+
"error WeightChangeAlreadyApproved()",
|
|
3267
|
+
"error WeightChangeNotApproved()",
|
|
3268
|
+
"error WeightChangeTimelockNotExpired()"
|
|
3269
|
+
];
|
|
3270
|
+
var WEIGHTED_SIGNATURE_ABI_PARSED = viem.parseAbi(WEIGHTED_SIGNATURE_ABI);
|
|
3271
|
+
var WEIGHT_CHANGE_TIMELOCK_SECONDS = 2 * 24 * 60 * 60;
|
|
3272
|
+
var WEIGHT_CHANGE_THRESHOLD = 2;
|
|
3273
|
+
var WEIGHT_CHANGE_EXPIRY_SECONDS = 30 * 24 * 60 * 60;
|
|
3274
|
+
var WEIGHT_CONFIG_FIELDS = [
|
|
3275
|
+
"passkeyWeight",
|
|
3276
|
+
"ecdsaWeight",
|
|
3277
|
+
"blsWeight",
|
|
3278
|
+
"guardian0Weight",
|
|
3279
|
+
"guardian1Weight",
|
|
3280
|
+
"guardian2Weight",
|
|
3281
|
+
"_padding",
|
|
3282
|
+
"tier1Threshold",
|
|
3283
|
+
"tier2Threshold",
|
|
3284
|
+
"tier3Threshold"
|
|
3285
|
+
];
|
|
3286
|
+
function toConfigTuple(config) {
|
|
3287
|
+
return WEIGHT_CONFIG_FIELDS.map((f) => config[f]);
|
|
3288
|
+
}
|
|
3289
|
+
function fromConfigResult(result) {
|
|
3290
|
+
const r = result;
|
|
3291
|
+
const pick = (name, idx) => Number(r[name] ?? r[idx]);
|
|
3292
|
+
return {
|
|
3293
|
+
passkeyWeight: pick("passkeyWeight", 0),
|
|
3294
|
+
ecdsaWeight: pick("ecdsaWeight", 1),
|
|
3295
|
+
blsWeight: pick("blsWeight", 2),
|
|
3296
|
+
guardian0Weight: pick("guardian0Weight", 3),
|
|
3297
|
+
guardian1Weight: pick("guardian1Weight", 4),
|
|
3298
|
+
guardian2Weight: pick("guardian2Weight", 5),
|
|
3299
|
+
_padding: pick("_padding", 6),
|
|
3300
|
+
tier1Threshold: pick("tier1Threshold", 7),
|
|
3301
|
+
tier2Threshold: pick("tier2Threshold", 8),
|
|
3302
|
+
tier3Threshold: pick("tier3Threshold", 9)
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
var WeightedSignatureService = class {
|
|
3306
|
+
constructor(accountAddress, client) {
|
|
3307
|
+
this.accountAddress = accountAddress;
|
|
3308
|
+
this.client = client;
|
|
3309
|
+
this.address = accountAddress;
|
|
3310
|
+
}
|
|
3311
|
+
address;
|
|
3312
|
+
// ── On-chain reads ──────────────────────────────────────────────
|
|
3313
|
+
/** Read the account's current active WeightConfig. */
|
|
3314
|
+
async getWeightConfig() {
|
|
3315
|
+
const result = await this.client.readContract({
|
|
3316
|
+
address: this.address,
|
|
3317
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3318
|
+
functionName: "weightConfig"
|
|
3319
|
+
});
|
|
3320
|
+
return fromConfigResult(result);
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Read the pending weight-change proposal. When `proposedAt === 0n` there is no
|
|
3324
|
+
* active proposal (the returned `proposed` config will be all zeros).
|
|
3325
|
+
*/
|
|
3326
|
+
async getPendingWeightChange() {
|
|
3327
|
+
const [proposed, proposedAt, approvalBitmap] = await this.client.readContract({
|
|
3328
|
+
address: this.address,
|
|
3329
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3330
|
+
functionName: "pendingWeightChange"
|
|
3331
|
+
});
|
|
3332
|
+
return {
|
|
3333
|
+
proposed: fromConfigResult(proposed),
|
|
3334
|
+
proposedAt: BigInt(proposedAt),
|
|
3335
|
+
approvalBitmap: BigInt(approvalBitmap)
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
// ── Calldata encoders (for UserOp or direct tx submission) ─────
|
|
3339
|
+
/**
|
|
3340
|
+
* Encode setWeightConfig calldata. OWNER only; for first-time setup or strengthening.
|
|
3341
|
+
* Weakening an existing config must go through encodeProposeWeightChange instead.
|
|
3342
|
+
*/
|
|
3343
|
+
encodeSetWeightConfig(config) {
|
|
3344
|
+
return viem.encodeFunctionData({
|
|
3345
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3346
|
+
functionName: "setWeightConfig",
|
|
3347
|
+
args: [toConfigTuple(config)]
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Encode proposeWeightChange calldata. OWNER only; opens a guardian-governed proposal
|
|
3352
|
+
* (required for any weakening). Subject to 2-of-3 approval + 2-day timelock before execute.
|
|
3353
|
+
*/
|
|
3354
|
+
encodeProposeWeightChange(config) {
|
|
3355
|
+
return viem.encodeFunctionData({
|
|
3356
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3357
|
+
functionName: "proposeWeightChange",
|
|
3358
|
+
args: [toConfigTuple(config)]
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
/** Encode approveWeightChange calldata. GUARDIAN only; each guardian may approve once. */
|
|
3362
|
+
encodeApproveWeightChange() {
|
|
3363
|
+
return viem.encodeFunctionData({
|
|
3364
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3365
|
+
functionName: "approveWeightChange"
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
/** Encode cancelWeightChange calldata. OWNER or any GUARDIAN may cancel a pending proposal. */
|
|
3369
|
+
encodeCancelWeightChange() {
|
|
3370
|
+
return viem.encodeFunctionData({
|
|
3371
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3372
|
+
functionName: "cancelWeightChange"
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3376
|
+
* Encode executeWeightChange calldata. Callable by anyone, but only succeeds once the
|
|
3377
|
+
* threshold (2-of-3) and timelock (2 days) are both satisfied and the proposal has not expired.
|
|
3378
|
+
*/
|
|
3379
|
+
encodeExecuteWeightChange() {
|
|
3380
|
+
return viem.encodeFunctionData({
|
|
3381
|
+
abi: WEIGHTED_SIGNATURE_ABI_PARSED,
|
|
3382
|
+
functionName: "executeWeightChange"
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
};
|
|
3386
|
+
var AGENT_REGISTRY_ABI = [
|
|
3387
|
+
// ── Writes ──
|
|
3388
|
+
"function registerAgent(address agentWallet, bytes agentWalletSig) external",
|
|
3389
|
+
"function revokeAgent(address agentWallet) external",
|
|
3390
|
+
"function deregisterAgent(address agentWallet) external",
|
|
3391
|
+
// ── Reads ──
|
|
3392
|
+
"function isRegisteredAgent(address agentWallet) external view returns (bool)",
|
|
3393
|
+
"function isValidAccount(address account) external view returns (bool)",
|
|
3394
|
+
"function getHumanOwner(address agentWallet) external view returns (address)",
|
|
3395
|
+
"function getAgentCount(address owner) external view returns (uint256)",
|
|
3396
|
+
"function getAgentByIndex(address owner, uint256 index) external view returns (address)",
|
|
3397
|
+
"function getAgents(address humanOwner) external view returns (address[])",
|
|
3398
|
+
"function getAgentsPage(address owner, uint256 start, uint256 count) external view returns (address[] page)",
|
|
3399
|
+
"function agentWalletOwner(address agentWallet) external view returns (address)",
|
|
3400
|
+
"function ownerAgents(address owner, uint256 index) external view returns (address)",
|
|
3401
|
+
"function balanceOf(address humanOwner) external view returns (uint256)",
|
|
3402
|
+
// ── Errors (for decoding reverts) ──
|
|
3403
|
+
"error AgentAlreadyRegistered()",
|
|
3404
|
+
"error CallerNotAirAccount()",
|
|
3405
|
+
"error InvalidAddress()",
|
|
3406
|
+
"error InvalidAgentSignature()",
|
|
3407
|
+
"error NotAgentOwner()",
|
|
3408
|
+
"error SelfRegistrationForbidden()"
|
|
3409
|
+
];
|
|
3410
|
+
var REGISTRY_ABI = viem.parseAbi(AGENT_REGISTRY_ABI);
|
|
3411
|
+
var FACTORY_ABI2 = viem.parseAbi(AIRACCOUNT_FACTORY_ABI);
|
|
3412
|
+
var ACCOUNT_ABI3 = viem.parseAbi(AIRACCOUNT_ABI);
|
|
3413
|
+
var AgentRegistryService = class {
|
|
3414
|
+
/**
|
|
3415
|
+
* @param client viem PublicClient for on-chain reads (e.g. `ethereum.getProvider()`).
|
|
3416
|
+
* @param registryAddress deployed AgentRegistry contract address.
|
|
3417
|
+
*/
|
|
3418
|
+
constructor(client, registryAddress) {
|
|
3419
|
+
this.registryAddress = registryAddress;
|
|
3420
|
+
this.client = client;
|
|
3421
|
+
}
|
|
3422
|
+
client;
|
|
3423
|
+
// ── Composed register/revoke-via-account scenario encoders ──────────────────
|
|
3424
|
+
// registerAgent/revokeAgent require msg.sender == the agent account, so they are delivered
|
|
3425
|
+
// through the account's execute(registry, 0, calldata). These return the FULL execute
|
|
3426
|
+
// calldata to submit TO the agent account (owner-signed) — the scenario-level entry point.
|
|
3427
|
+
/** Encode `account.execute(registry, 0, registerAgent(agentWallet, agentWalletSig))`. */
|
|
3428
|
+
encodeRegisterAgentViaAccount(agentWallet, agentWalletSig) {
|
|
3429
|
+
return viem.encodeFunctionData({
|
|
3430
|
+
abi: ACCOUNT_ABI3,
|
|
3431
|
+
functionName: "execute",
|
|
3432
|
+
args: [
|
|
3433
|
+
this.registryAddress,
|
|
3434
|
+
0n,
|
|
3435
|
+
this.encodeRegisterAgent(agentWallet, agentWalletSig)
|
|
3436
|
+
]
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
/** Encode `account.execute(registry, 0, revokeAgent(agentWallet))`. */
|
|
3440
|
+
encodeRevokeAgentViaAccount(agentWallet) {
|
|
3441
|
+
return viem.encodeFunctionData({
|
|
3442
|
+
abi: ACCOUNT_ABI3,
|
|
3443
|
+
functionName: "execute",
|
|
3444
|
+
args: [this.registryAddress, 0n, this.encodeRevokeAgent(agentWallet)]
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
// ── AgentRegistry calldata encoders ─────────────────────────────────────────
|
|
3448
|
+
/**
|
|
3449
|
+
* Encode calldata for `registerAgent(agentWallet, agentWalletSig)`.
|
|
3450
|
+
*
|
|
3451
|
+
* Binds `agentWallet` to the caller (the human owner). `agentWalletSig` must be the agent
|
|
3452
|
+
* wallet's EIP-191 signature proving control of the key. Reverts with
|
|
3453
|
+
* `SelfRegistrationForbidden` if the caller registers itself, or `AgentAlreadyRegistered`
|
|
3454
|
+
* if the wallet is already bound.
|
|
3455
|
+
*/
|
|
3456
|
+
encodeRegisterAgent(agentWallet, agentWalletSig) {
|
|
3457
|
+
return viem.encodeFunctionData({
|
|
3458
|
+
abi: REGISTRY_ABI,
|
|
3459
|
+
functionName: "registerAgent",
|
|
3460
|
+
args: [agentWallet, agentWalletSig]
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
/**
|
|
3464
|
+
* Encode calldata for `revokeAgent(agentWallet)`.
|
|
3465
|
+
*
|
|
3466
|
+
* Owner-initiated revocation of a previously registered agent wallet. Caller must be the
|
|
3467
|
+
* agent's human owner (else `NotAgentOwner`).
|
|
3468
|
+
*/
|
|
3469
|
+
encodeRevokeAgent(agentWallet) {
|
|
3470
|
+
return viem.encodeFunctionData({
|
|
3471
|
+
abi: REGISTRY_ABI,
|
|
3472
|
+
functionName: "revokeAgent",
|
|
3473
|
+
args: [agentWallet]
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
/**
|
|
3477
|
+
* Encode calldata for `deregisterAgent(agentWallet)`.
|
|
3478
|
+
*
|
|
3479
|
+
* Removes the agent wallet from the registry (full deregistration, distinct from the
|
|
3480
|
+
* lighter-weight `revokeAgent`). Caller must be the agent's human owner.
|
|
3481
|
+
*/
|
|
3482
|
+
encodeDeregisterAgent(agentWallet) {
|
|
3483
|
+
return viem.encodeFunctionData({
|
|
3484
|
+
abi: REGISTRY_ABI,
|
|
3485
|
+
functionName: "deregisterAgent",
|
|
3486
|
+
args: [agentWallet]
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
// ── AgentRegistry on-chain reads ────────────────────────────────────────────
|
|
3490
|
+
/** Whether `agentWallet` is currently registered in the registry. */
|
|
3491
|
+
async isRegisteredAgent(agentWallet) {
|
|
3492
|
+
return await this.client.readContract({
|
|
3493
|
+
address: this.registryAddress,
|
|
3494
|
+
abi: REGISTRY_ABI,
|
|
3495
|
+
functionName: "isRegisteredAgent",
|
|
3496
|
+
args: [agentWallet]
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
/** Whether `account` has been marked valid (e.g. an AirAccount minted by the bound factory). */
|
|
3500
|
+
async isValidAccount(account) {
|
|
3501
|
+
return await this.client.readContract({
|
|
3502
|
+
address: this.registryAddress,
|
|
3503
|
+
abi: REGISTRY_ABI,
|
|
3504
|
+
functionName: "isValidAccount",
|
|
3505
|
+
args: [account]
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
/** The human owner bound to `agentWallet` (ZeroAddress if unregistered). */
|
|
3509
|
+
async getHumanOwner(agentWallet) {
|
|
3510
|
+
return await this.client.readContract({
|
|
3511
|
+
address: this.registryAddress,
|
|
3512
|
+
abi: REGISTRY_ABI,
|
|
3513
|
+
functionName: "getHumanOwner",
|
|
3514
|
+
args: [agentWallet]
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3517
|
+
/** Number of agents registered under `owner`. */
|
|
3518
|
+
async getAgentCount(owner) {
|
|
3519
|
+
return BigInt(
|
|
3520
|
+
await this.client.readContract({
|
|
3521
|
+
address: this.registryAddress,
|
|
3522
|
+
abi: REGISTRY_ABI,
|
|
3523
|
+
functionName: "getAgentCount",
|
|
3524
|
+
args: [owner]
|
|
3525
|
+
})
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
/** The agent wallet at `index` in `owner`'s agent list. */
|
|
3529
|
+
async getAgentByIndex(owner, index) {
|
|
3530
|
+
return await this.client.readContract({
|
|
3531
|
+
address: this.registryAddress,
|
|
3532
|
+
abi: REGISTRY_ABI,
|
|
3533
|
+
functionName: "getAgentByIndex",
|
|
3534
|
+
args: [owner, BigInt(index)]
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
/** Full list of agent wallets registered under `humanOwner`. */
|
|
3538
|
+
async getAgents(humanOwner) {
|
|
3539
|
+
const result = await this.client.readContract({
|
|
3540
|
+
address: this.registryAddress,
|
|
3541
|
+
abi: REGISTRY_ABI,
|
|
3542
|
+
functionName: "getAgents",
|
|
3543
|
+
args: [humanOwner]
|
|
3544
|
+
});
|
|
3545
|
+
return Array.from(result);
|
|
3546
|
+
}
|
|
3547
|
+
/**
|
|
3548
|
+
* Paginated slice of `owner`'s agent wallets: `count` entries starting at `start`.
|
|
3549
|
+
* The contract clamps `count` to the remaining length, so the returned array may be shorter.
|
|
3550
|
+
*/
|
|
3551
|
+
async getAgentsPage(owner, start, count) {
|
|
3552
|
+
const result = await this.client.readContract({
|
|
3553
|
+
address: this.registryAddress,
|
|
3554
|
+
abi: REGISTRY_ABI,
|
|
3555
|
+
functionName: "getAgentsPage",
|
|
3556
|
+
args: [owner, BigInt(start), BigInt(count)]
|
|
3557
|
+
});
|
|
3558
|
+
return Array.from(result);
|
|
3559
|
+
}
|
|
3560
|
+
/** Raw `agentWalletOwner` mapping read (agentWallet → owner). */
|
|
3561
|
+
async agentWalletOwner(agentWallet) {
|
|
3562
|
+
return await this.client.readContract({
|
|
3563
|
+
address: this.registryAddress,
|
|
3564
|
+
abi: REGISTRY_ABI,
|
|
3565
|
+
functionName: "agentWalletOwner",
|
|
3566
|
+
args: [agentWallet]
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
/** Raw `ownerAgents` array read (owner, index → agentWallet). */
|
|
3570
|
+
async ownerAgents(owner, index) {
|
|
3571
|
+
return await this.client.readContract({
|
|
3572
|
+
address: this.registryAddress,
|
|
3573
|
+
abi: REGISTRY_ABI,
|
|
3574
|
+
functionName: "ownerAgents",
|
|
3575
|
+
args: [owner, BigInt(index)]
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
// ── Factory agent-account helpers (AAStarAirAccountFactoryV7) ───────────────
|
|
3579
|
+
/**
|
|
3580
|
+
* Encode calldata for the factory's `createAgentAccount(...)`.
|
|
3581
|
+
*
|
|
3582
|
+
* Targets the AAStarAirAccountFactoryV7, NOT the AgentRegistry. The factory deploys the agent
|
|
3583
|
+
* AirAccount and registers it in the bound AgentRegistry in one transaction. Submit this
|
|
3584
|
+
* calldata to the factory address (direct tx or via a relayer).
|
|
3585
|
+
*/
|
|
3586
|
+
encodeCreateAgentAccount(params) {
|
|
3587
|
+
return viem.encodeFunctionData({
|
|
3588
|
+
abi: FACTORY_ABI2,
|
|
3589
|
+
functionName: "createAgentAccount",
|
|
3590
|
+
args: [
|
|
3591
|
+
params.agentKey,
|
|
3592
|
+
params.agentId,
|
|
3593
|
+
params.guardian2,
|
|
3594
|
+
params.guardian2Sig,
|
|
3595
|
+
params.agentKeySig,
|
|
3596
|
+
BigInt(params.deadline),
|
|
3597
|
+
params.dailyLimit
|
|
3598
|
+
]
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
/**
|
|
3602
|
+
* Encode calldata for the factory's `setAgentRegistry(_agentRegistry)` (factory-admin only).
|
|
3603
|
+
*/
|
|
3604
|
+
encodeSetAgentRegistry(agentRegistry) {
|
|
3605
|
+
return viem.encodeFunctionData({
|
|
3606
|
+
abi: FACTORY_ABI2,
|
|
3607
|
+
functionName: "setAgentRegistry",
|
|
3608
|
+
args: [agentRegistry]
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Predict the CREATE2 address of an agent account via the factory's `getAgentAddress(...)`.
|
|
3613
|
+
*
|
|
3614
|
+
* @param factoryAddress AAStarAirAccountFactoryV7 address.
|
|
3615
|
+
* @param humanOwner the human guardian/owner co-owning the agent account.
|
|
3616
|
+
* @param agentKey the agent's signing key.
|
|
3617
|
+
* @param agentId the bytes32 agent identifier.
|
|
3618
|
+
*/
|
|
3619
|
+
async getAgentAccountAddress(factoryAddress, humanOwner, agentKey, agentId) {
|
|
3620
|
+
return await this.client.readContract({
|
|
3621
|
+
address: factoryAddress,
|
|
3622
|
+
abi: FACTORY_ABI2,
|
|
3623
|
+
functionName: "getAgentAddress",
|
|
3624
|
+
args: [humanOwner, agentKey, agentId]
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
/** Read the AgentRegistry address currently bound to the factory. */
|
|
3628
|
+
async getFactoryAgentRegistry(factoryAddress) {
|
|
3629
|
+
return await this.client.readContract({
|
|
3630
|
+
address: factoryAddress,
|
|
3631
|
+
abi: FACTORY_ABI2,
|
|
3632
|
+
functionName: "agentRegistry",
|
|
3633
|
+
args: []
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
var ERC8004_ABI = [
|
|
3638
|
+
"function setAgentWallet(uint256 agentId, address agentWallet, address agentRegistry, bytes agentWalletSig) external",
|
|
3639
|
+
"function mintAgentIdentity(address identityRegistry, string agentURI) external returns (uint256 agentId)",
|
|
3640
|
+
"function bindERC8004AgentWallet(address identityRegistry, uint256 agentId, address agentWallet, uint256 deadline, bytes signature) external",
|
|
3641
|
+
"function submitAgentReputation(address reputationRegistry, uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external",
|
|
3642
|
+
"function queryAgentReputation(address reputationRegistry, uint256 agentId, address[] clientAddresses, string tag1, string tag2) external view returns (uint64 count, int128 summaryValue, uint8 summaryDecimals)",
|
|
3643
|
+
"function agentExtension() external view returns (address)"
|
|
3644
|
+
];
|
|
3645
|
+
var ERC8004_ADDRESSES = {
|
|
3646
|
+
mainnet: {
|
|
3647
|
+
identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
3648
|
+
reputationRegistry: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
|
|
3649
|
+
validationRegistry: "0x8004Cc8439f36fd5F9F049D9fF86523Df6dAAB58"
|
|
3650
|
+
},
|
|
3651
|
+
testnet: {
|
|
3652
|
+
identityRegistry: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
|
|
3653
|
+
reputationRegistry: "0x8004B663056A597Dffe9eCcC1965A193B7388713",
|
|
3654
|
+
validationRegistry: "0x8004Cb1BF31DAf7788923b405b754f57acEB4272"
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
var MAINNET_CHAIN_IDS = /* @__PURE__ */ new Set([1, 10, 137, 8453, 42161, 43114, 56, 534352, 100, 42220, 59144, 5e3, 167e3, 360]);
|
|
3658
|
+
var TESTNET_CHAIN_IDS = /* @__PURE__ */ new Set([11155111, 11155420, 84532, 421614, 80002]);
|
|
3659
|
+
function erc8004AddressesForChain(chainId) {
|
|
3660
|
+
if (MAINNET_CHAIN_IDS.has(chainId)) return ERC8004_ADDRESSES.mainnet;
|
|
3661
|
+
if (TESTNET_CHAIN_IDS.has(chainId)) return ERC8004_ADDRESSES.testnet;
|
|
3662
|
+
throw new Error(`ERC-8004: unsupported chain ${chainId}`);
|
|
3663
|
+
}
|
|
3664
|
+
var ERC8004Service = class {
|
|
3665
|
+
abi;
|
|
3666
|
+
provider;
|
|
3667
|
+
constructor(provider) {
|
|
3668
|
+
this.abi = viem.parseAbi(ERC8004_ABI);
|
|
3669
|
+
this.provider = provider;
|
|
3670
|
+
}
|
|
3671
|
+
/**
|
|
3672
|
+
* Build a read-only viem contract bound to the account address. The ABI is loaded from
|
|
3673
|
+
* human-readable signatures via `parseAbi` (loose `Abi`), so `read` methods are indexed by
|
|
3674
|
+
* name and return `unknown` — cast at the call site. Mirrors the dynamic surface that
|
|
3675
|
+
* `ethers.Contract` previously exposed. Caller must ensure `this.provider` is set.
|
|
3676
|
+
*/
|
|
3677
|
+
contractAt(accountAddress) {
|
|
3678
|
+
return viem.getContract({
|
|
3679
|
+
address: accountAddress,
|
|
3680
|
+
abi: this.abi,
|
|
3681
|
+
client: this.provider
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
// ── AAStar AgentRegistry path ─────────────────────────────────────────────
|
|
3685
|
+
/**
|
|
3686
|
+
* Encode calldata for `setAgentWallet`.
|
|
3687
|
+
*
|
|
3688
|
+
* Registers `agentWallet` in the AAStar AgentRegistry (SuperPaymaster-facing). This is the
|
|
3689
|
+
* correct path when the goal is SuperPaymaster gasless sponsorship for the agent.
|
|
3690
|
+
*
|
|
3691
|
+
* **Not** a call to the official ERC-8004 IdentityRegistry. Use `encodeMintAgentIdentity`
|
|
3692
|
+
* + `encodeBindERC8004AgentWallet` for the ERC-8004 standard path.
|
|
3693
|
+
*
|
|
3694
|
+
* Callable: owner EOA direct tx OR via UserOp (gasless).
|
|
3695
|
+
*/
|
|
3696
|
+
encodeSetAgentWallet(params) {
|
|
3697
|
+
return viem.encodeFunctionData({
|
|
3698
|
+
abi: this.abi,
|
|
3699
|
+
functionName: "setAgentWallet",
|
|
3700
|
+
args: [params.agentId, params.agentWallet, params.agentRegistry, params.agentWalletSig]
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
// ── Official ERC-8004 identity path ──────────────────────────────────────
|
|
3704
|
+
/**
|
|
3705
|
+
* Encode calldata for `mintAgentIdentity`.
|
|
3706
|
+
*
|
|
3707
|
+
* Mints an ERC-721 agent identity NFT in the official ERC-8004 IdentityRegistry and returns
|
|
3708
|
+
* the new `agentId` (decoded from the tx receipt). The `identityRegistry` must be
|
|
3709
|
+
* `erc8004AddressesForChain(chainId).identityRegistry` — the contract reverts otherwise.
|
|
3710
|
+
*
|
|
3711
|
+
* Callable: owner EOA direct tx OR via UserOp (gasless).
|
|
3712
|
+
*/
|
|
3713
|
+
encodeMintAgentIdentity(params) {
|
|
3714
|
+
return viem.encodeFunctionData({
|
|
3715
|
+
abi: this.abi,
|
|
3716
|
+
functionName: "mintAgentIdentity",
|
|
3717
|
+
args: [params.identityRegistry, params.agentURI]
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
/**
|
|
3721
|
+
* Encode calldata for `bindERC8004AgentWallet`.
|
|
3722
|
+
*
|
|
3723
|
+
* Binds an execution wallet to an existing ERC-8004 agent identity NFT. Requires a
|
|
3724
|
+
* deadline-bounded signature from the expected signer (see the IdentityRegistry contract).
|
|
3725
|
+
* The `identityRegistry` must be the official chain-specific address.
|
|
3726
|
+
*
|
|
3727
|
+
* Callable: owner EOA direct tx OR via UserOp (gasless).
|
|
3728
|
+
*/
|
|
3729
|
+
encodeBindERC8004AgentWallet(params) {
|
|
3730
|
+
return viem.encodeFunctionData({
|
|
3731
|
+
abi: this.abi,
|
|
3732
|
+
functionName: "bindERC8004AgentWallet",
|
|
3733
|
+
args: [
|
|
3734
|
+
params.identityRegistry,
|
|
3735
|
+
params.agentId,
|
|
3736
|
+
params.agentWallet,
|
|
3737
|
+
params.deadline,
|
|
3738
|
+
params.signature
|
|
3739
|
+
]
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
// ── Reputation ────────────────────────────────────────────────────────────
|
|
3743
|
+
/**
|
|
3744
|
+
* Encode calldata for `submitAgentReputation`.
|
|
3745
|
+
*
|
|
3746
|
+
* Submits a reputation feedback entry to the official ERC-8004 ReputationRegistry.
|
|
3747
|
+
* `reputationRegistry` must be `erc8004AddressesForChain(chainId).reputationRegistry`.
|
|
3748
|
+
*
|
|
3749
|
+
* Callable: owner EOA direct tx OR via UserOp (gasless).
|
|
3750
|
+
*/
|
|
3751
|
+
encodeSubmitAgentReputation(params) {
|
|
3752
|
+
return viem.encodeFunctionData({
|
|
3753
|
+
abi: this.abi,
|
|
3754
|
+
functionName: "submitAgentReputation",
|
|
3755
|
+
args: [
|
|
3756
|
+
params.reputationRegistry,
|
|
3757
|
+
params.agentId,
|
|
3758
|
+
params.value,
|
|
3759
|
+
params.valueDecimals,
|
|
3760
|
+
params.tag1,
|
|
3761
|
+
params.tag2,
|
|
3762
|
+
params.endpoint,
|
|
3763
|
+
params.feedbackURI,
|
|
3764
|
+
params.feedbackHash
|
|
3765
|
+
]
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Query aggregated reputation for an agent from the official ERC-8004 ReputationRegistry.
|
|
3770
|
+
* Returns `null` when no provider was supplied at construction.
|
|
3771
|
+
*/
|
|
3772
|
+
async queryAgentReputation(accountAddress, params) {
|
|
3773
|
+
if (!this.provider) throw new Error("ERC8004Service: provider required for on-chain reads");
|
|
3774
|
+
const contract = this.contractAt(accountAddress);
|
|
3775
|
+
const [count, summaryValue, summaryDecimals] = await contract.read.queryAgentReputation([
|
|
3776
|
+
params.reputationRegistry,
|
|
3777
|
+
params.agentId,
|
|
3778
|
+
params.clientAddresses,
|
|
3779
|
+
params.tag1,
|
|
3780
|
+
params.tag2
|
|
3781
|
+
]);
|
|
3782
|
+
return { count: BigInt(count), summaryValue: BigInt(summaryValue), summaryDecimals: Number(summaryDecimals) };
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Encode calldata for `queryAgentReputation` (for static-call or eth_call without a signer).
|
|
3786
|
+
*/
|
|
3787
|
+
encodeQueryAgentReputation(params) {
|
|
3788
|
+
return viem.encodeFunctionData({
|
|
3789
|
+
abi: this.abi,
|
|
3790
|
+
functionName: "queryAgentReputation",
|
|
3791
|
+
args: [
|
|
3792
|
+
params.reputationRegistry,
|
|
3793
|
+
params.agentId,
|
|
3794
|
+
params.clientAddresses,
|
|
3795
|
+
params.tag1,
|
|
3796
|
+
params.tag2
|
|
3797
|
+
]
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Read the agentExtension implementation address from a deployed AirAccount.
|
|
3802
|
+
*/
|
|
3803
|
+
async getAgentExtensionAddress(accountAddress) {
|
|
3804
|
+
if (!this.provider) throw new Error("ERC8004Service: provider required for on-chain reads");
|
|
3805
|
+
const contract = this.contractAt(accountAddress);
|
|
3806
|
+
const extension = await contract.read.agentExtension([]);
|
|
3807
|
+
return extension;
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
var DEFAULT_KMS_ENDPOINT = "https://kms.aastar.io";
|
|
3811
|
+
var KmsHttpClient = class {
|
|
3812
|
+
endpoint;
|
|
3813
|
+
enabled;
|
|
3814
|
+
logger;
|
|
3815
|
+
apiKey;
|
|
3816
|
+
http;
|
|
3817
|
+
constructor(options) {
|
|
3818
|
+
this.endpoint = options.kmsEndpoint ?? DEFAULT_KMS_ENDPOINT;
|
|
3819
|
+
this.enabled = options.kmsEnabled === true;
|
|
3820
|
+
this.apiKey = options.kmsApiKey;
|
|
3821
|
+
this.logger = options.logger ?? new ConsoleLogger("[KmsHttpClient]");
|
|
3822
|
+
const headers = { "Content-Type": "application/json" };
|
|
3823
|
+
if (this.apiKey) {
|
|
3824
|
+
headers["x-api-key"] = this.apiKey;
|
|
3825
|
+
}
|
|
3826
|
+
this.http = axios__default.default.create({ baseURL: this.endpoint, headers });
|
|
3827
|
+
}
|
|
3828
|
+
/** Throw if KMS is not enabled — every operation must call this first. */
|
|
3829
|
+
ensureEnabled() {
|
|
3830
|
+
if (!this.enabled) {
|
|
3831
|
+
throw new Error("KMS service is not enabled");
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
/**
|
|
3835
|
+
* Plain JSON POST. The axios `config` arg is only forwarded when defined, so a
|
|
3836
|
+
* config-less call results in `http.post(path, body)` (2 args) — preserving the
|
|
3837
|
+
* exact call shape the existing unit tests assert against.
|
|
3838
|
+
*/
|
|
3839
|
+
async post(path, body, config) {
|
|
3840
|
+
const response = config === void 0 ? await this.http.post(path, body) : await this.http.post(path, body, config);
|
|
3841
|
+
return response.data;
|
|
3842
|
+
}
|
|
3843
|
+
/** Plain JSON GET. */
|
|
3844
|
+
async get(path, config) {
|
|
3845
|
+
const response = config === void 0 ? await this.http.get(path) : await this.http.get(path, config);
|
|
3846
|
+
return response.data;
|
|
3847
|
+
}
|
|
3848
|
+
/** POST with AWS-KMS framing (x-amz-target header) — required for wallet/signing ops. */
|
|
3849
|
+
async amzPost(path, target, body) {
|
|
3850
|
+
return this.post(path, body, {
|
|
3851
|
+
headers: {
|
|
3852
|
+
"Content-Type": "application/x-amz-json-1.1",
|
|
3853
|
+
"x-amz-target": target
|
|
3854
|
+
}
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
/** POST authenticated with a TEE-issued agent/session JWT (Authorization: Bearer). */
|
|
3858
|
+
async postWithBearer(path, body, jwt) {
|
|
3859
|
+
return this.post(path, body, {
|
|
3860
|
+
headers: { authorization: `Bearer ${jwt}` }
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
};
|
|
3864
|
+
|
|
3865
|
+
// ../../node_modules/.pnpm/@noble+curves@2.0.1/node_modules/@noble/curves/nist.js
|
|
3866
|
+
var p256_CURVE = /* @__PURE__ */ (() => ({
|
|
3867
|
+
p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
|
|
3868
|
+
n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
|
|
3869
|
+
h: BigInt(1),
|
|
3870
|
+
a: BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"),
|
|
3871
|
+
b: BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"),
|
|
3872
|
+
Gx: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"),
|
|
3873
|
+
Gy: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
|
|
3874
|
+
}))();
|
|
3875
|
+
var p256_Point = /* @__PURE__ */ chunkXQROKLZI_cjs.weierstrass(p256_CURVE);
|
|
3876
|
+
var p256 = /* @__PURE__ */ chunkXQROKLZI_cjs.ecdsa(p256_Point, chunkXQROKLZI_cjs.sha256);
|
|
3877
|
+
|
|
3878
|
+
// ../airaccount/src/server/services/webauthn-ceremony.ts
|
|
3879
|
+
var DEFAULT_RP_ID = "aastar.io";
|
|
3880
|
+
var DEFAULT_ORIGIN = "https://aastar.io";
|
|
3881
|
+
var DEFAULT_CREDENTIAL_ID = "dGVzdC1jcmVkZW50aWFs";
|
|
3882
|
+
function base64UrlEncode(bytes) {
|
|
3883
|
+
return Buffer.from(bytes).toString("base64url");
|
|
3884
|
+
}
|
|
3885
|
+
function base64UrlDecode(value) {
|
|
3886
|
+
return new Uint8Array(Buffer.from(value, "base64url"));
|
|
3887
|
+
}
|
|
3888
|
+
function hexToBytes4(hex) {
|
|
3889
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
3890
|
+
if (clean.length % 2 !== 0) {
|
|
3891
|
+
throw new Error("hexToBytes: odd-length hex string");
|
|
3892
|
+
}
|
|
3893
|
+
const out = new Uint8Array(clean.length / 2);
|
|
3894
|
+
for (let i = 0; i < out.length; i++) {
|
|
3895
|
+
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
3896
|
+
}
|
|
3897
|
+
return out;
|
|
3898
|
+
}
|
|
3899
|
+
var P256PasskeySigner = class {
|
|
3900
|
+
credentialId;
|
|
3901
|
+
privateKey;
|
|
3902
|
+
/**
|
|
3903
|
+
* @param privateKey raw 32-byte P-256 scalar (Uint8Array or hex, 0x optional).
|
|
3904
|
+
* @param credentialId base64url credential id (defaults to the reference fixture).
|
|
3905
|
+
*/
|
|
3906
|
+
constructor(privateKey, credentialId = DEFAULT_CREDENTIAL_ID) {
|
|
3907
|
+
this.privateKey = typeof privateKey === "string" ? hexToBytes4(privateKey) : privateKey;
|
|
3908
|
+
this.credentialId = credentialId;
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* Uncompressed (0x04…, 65-byte) P-256 public key hex. Register this with the
|
|
3912
|
+
* KMS via CreateKey `PasskeyPublicKey` (or ChangePasskey) so the TA can verify
|
|
3913
|
+
* assertions produced by this signer.
|
|
3914
|
+
*/
|
|
3915
|
+
get publicKeyHex() {
|
|
3916
|
+
return "0x" + Buffer.from(p256.getPublicKey(this.privateKey, false)).toString("hex");
|
|
3917
|
+
}
|
|
3918
|
+
sign(message) {
|
|
3919
|
+
return p256.sign(message, this.privateKey, { prehash: true, format: "der" });
|
|
3920
|
+
}
|
|
3921
|
+
};
|
|
3922
|
+
function buildClientDataJSON(challenge, origin = DEFAULT_ORIGIN) {
|
|
3923
|
+
const json = JSON.stringify({ type: "webauthn.get", challenge, origin });
|
|
3924
|
+
return new TextEncoder().encode(json);
|
|
3925
|
+
}
|
|
3926
|
+
function buildAuthenticatorData(rpId = DEFAULT_RP_ID, signCount = 1) {
|
|
3927
|
+
const rpIdHash = crypto.createHash("sha256").update(rpId).digest();
|
|
3928
|
+
const out = new Uint8Array(37);
|
|
3929
|
+
out.set(rpIdHash, 0);
|
|
3930
|
+
out[32] = 5;
|
|
3931
|
+
new DataView(out.buffer).setUint32(33, signCount >>> 0, false);
|
|
3932
|
+
return out;
|
|
3933
|
+
}
|
|
3934
|
+
async function buildAuthenticationCredential(opts) {
|
|
3935
|
+
const origin = opts.origin ?? DEFAULT_ORIGIN;
|
|
3936
|
+
const rpId = opts.rpId ?? DEFAULT_RP_ID;
|
|
3937
|
+
const signCount = opts.signCount ?? 1;
|
|
3938
|
+
const clientDataJSON = buildClientDataJSON(opts.challenge, origin);
|
|
3939
|
+
const authenticatorData = buildAuthenticatorData(rpId, signCount);
|
|
3940
|
+
const clientDataHash = crypto.createHash("sha256").update(clientDataJSON).digest();
|
|
3941
|
+
const message = new Uint8Array(authenticatorData.length + clientDataHash.length);
|
|
3942
|
+
message.set(authenticatorData, 0);
|
|
3943
|
+
message.set(clientDataHash, authenticatorData.length);
|
|
3944
|
+
const signature = await opts.signer.sign(message);
|
|
3945
|
+
return {
|
|
3946
|
+
id: opts.signer.credentialId,
|
|
3947
|
+
rawId: opts.signer.credentialId,
|
|
3948
|
+
type: "public-key",
|
|
3949
|
+
response: {
|
|
3950
|
+
clientDataJSON: base64UrlEncode(clientDataJSON),
|
|
3951
|
+
authenticatorData: base64UrlEncode(authenticatorData),
|
|
3952
|
+
signature: base64UrlEncode(signature)
|
|
3953
|
+
}
|
|
3954
|
+
};
|
|
3955
|
+
}
|
|
3956
|
+
async function runWebAuthnCeremony(begin, options) {
|
|
3957
|
+
const begun = await begin();
|
|
3958
|
+
const challenge = begun?.Options?.challenge;
|
|
3959
|
+
if (!begun?.ChallengeId || !challenge) {
|
|
3960
|
+
throw new Error(
|
|
3961
|
+
"WebAuthn ceremony: begin endpoint did not return a ChallengeId + Options.challenge"
|
|
3962
|
+
);
|
|
3963
|
+
}
|
|
3964
|
+
const credential = await buildAuthenticationCredential({
|
|
3965
|
+
challenge,
|
|
3966
|
+
signer: options.signer,
|
|
3967
|
+
rpId: options.rpId,
|
|
3968
|
+
origin: options.origin,
|
|
3969
|
+
signCount: options.signCount
|
|
3970
|
+
});
|
|
3971
|
+
return { ChallengeId: begun.ChallengeId, Credential: credential };
|
|
3972
|
+
}
|
|
3973
|
+
function beginAuthenticationChallenge(http2, keyId) {
|
|
3974
|
+
return http2.post("/BeginAuthentication", { KeyId: keyId });
|
|
3975
|
+
}
|
|
3976
|
+
function beginGrantSessionChallenge(http2, keyId) {
|
|
3977
|
+
return http2.get("/kms/begin-grant-session-auth", {
|
|
3978
|
+
params: { keyId }
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
function runAuthenticationCeremony(http2, keyId, signer, options) {
|
|
3982
|
+
return runWebAuthnCeremony(() => beginAuthenticationChallenge(http2, keyId), {
|
|
3983
|
+
signer,
|
|
3984
|
+
...options
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
function runGrantSessionCeremony(http2, keyId, signer, options) {
|
|
3988
|
+
return runWebAuthnCeremony(() => beginGrantSessionChallenge(http2, keyId), {
|
|
3989
|
+
signer,
|
|
3990
|
+
...options
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
// ../airaccount/src/server/services/kms-signer.ts
|
|
3995
|
+
var KmsManager = class {
|
|
3996
|
+
client;
|
|
3997
|
+
logger;
|
|
3998
|
+
constructor(options) {
|
|
3999
|
+
this.client = new KmsHttpClient(options);
|
|
4000
|
+
this.logger = this.client.logger;
|
|
4001
|
+
}
|
|
4002
|
+
isKmsEnabled() {
|
|
4003
|
+
return this.client.enabled;
|
|
4004
|
+
}
|
|
4005
|
+
/** Shared HTTP transport — pass to KmsAgentService / KmsSessionService / etc. */
|
|
4006
|
+
get httpClient() {
|
|
4007
|
+
return this.client;
|
|
4008
|
+
}
|
|
4009
|
+
ensureEnabled() {
|
|
4010
|
+
this.client.ensureEnabled();
|
|
4011
|
+
}
|
|
4012
|
+
/** POST with x-amz-target header (required for wallet/signing operations). */
|
|
4013
|
+
async amzPost(path, target, body) {
|
|
4014
|
+
return this.client.amzPost(path, target, body);
|
|
4015
|
+
}
|
|
4016
|
+
// ── Key Management ──────────────────────────────────────────────
|
|
4017
|
+
async createKey(description, passkeyPublicKey) {
|
|
4018
|
+
this.ensureEnabled();
|
|
4019
|
+
return this.amzPost("/CreateKey", "TrentService.CreateKey", {
|
|
4020
|
+
Description: description,
|
|
4021
|
+
KeyUsage: "SIGN_VERIFY",
|
|
4022
|
+
KeySpec: "ECC_SECG_P256K1",
|
|
4023
|
+
Origin: "EXTERNAL_KMS",
|
|
4024
|
+
PasskeyPublicKey: passkeyPublicKey
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
async getKeyStatus(keyId) {
|
|
4028
|
+
this.ensureEnabled();
|
|
4029
|
+
return this.client.get("/KeyStatus", {
|
|
4030
|
+
params: { KeyId: keyId }
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
async describeKey(keyId) {
|
|
4034
|
+
this.ensureEnabled();
|
|
4035
|
+
return this.amzPost("/DescribeKey", "TrentService.DescribeKey", { KeyId: keyId });
|
|
4036
|
+
}
|
|
4037
|
+
/** Get a key's public key (uncompressed). Not WebAuthn-gated. */
|
|
4038
|
+
async getPublicKey(target) {
|
|
4039
|
+
this.ensureEnabled();
|
|
4040
|
+
return this.amzPost("/GetPublicKey", "TrentService.GetPublicKey", target);
|
|
4041
|
+
}
|
|
4042
|
+
/**
|
|
4043
|
+
* Derive an Ethereum address at a BIP-44 path (WebAuthn-gated).
|
|
4044
|
+
* Provide a WebAuthn ceremony assertion (preferred) or a Legacy passkey assertion.
|
|
4045
|
+
*/
|
|
4046
|
+
async deriveAddress(params) {
|
|
4047
|
+
this.ensureEnabled();
|
|
4048
|
+
return this.amzPost("/DeriveAddress", "TrentService.DeriveAddress", params);
|
|
4049
|
+
}
|
|
4050
|
+
/** List keys (paginated). Not WebAuthn-gated. */
|
|
4051
|
+
async listKeys(params = {}) {
|
|
4052
|
+
this.ensureEnabled();
|
|
4053
|
+
return this.amzPost("/ListKeys", "TrentService.ListKeys", params);
|
|
4054
|
+
}
|
|
4055
|
+
/**
|
|
4056
|
+
* Schedule key deletion (AWS-KMS action ScheduleKeyDeletion; WebAuthn-gated).
|
|
4057
|
+
* RPMB-bound on the TEE — requires a passkey/WebAuthn assertion on the normal path.
|
|
4058
|
+
*/
|
|
4059
|
+
async deleteKey(params) {
|
|
4060
|
+
this.ensureEnabled();
|
|
4061
|
+
return this.amzPost("/DeleteKey", "TrentService.ScheduleKeyDeletion", params);
|
|
4062
|
+
}
|
|
4063
|
+
/**
|
|
4064
|
+
* Unfreeze a dormant (frozen) key (issue #42; WebAuthn-gated).
|
|
4065
|
+
* A key auto-frozen by the dormant-key sweep rejects signing until unfrozen.
|
|
4066
|
+
* The TEE verifies the owner via the same strict WebAuthn ceremony as
|
|
4067
|
+
* {@link deleteKey}; ownership is checked even when the key is already active,
|
|
4068
|
+
* so this cannot be used as an unauthenticated key-state probe. Unlike DeleteKey
|
|
4069
|
+
* this endpoint takes no `x-amz-target` header — it authenticates via the default
|
|
4070
|
+
* API key plus the WebAuthn assertion in the body.
|
|
4071
|
+
*/
|
|
4072
|
+
async unfreezeKey(params) {
|
|
4073
|
+
this.ensureEnabled();
|
|
4074
|
+
return this.client.post("/UnfreezeKey", params);
|
|
4075
|
+
}
|
|
4076
|
+
/**
|
|
4077
|
+
* Rotate the WebAuthn passkey bound to a key (WebAuthn-gated, RPMB-bound).
|
|
4078
|
+
* `PasskeyPublicKey` is the NEW P-256 public key (0x04… 65-byte uncompressed).
|
|
4079
|
+
*/
|
|
4080
|
+
async changePasskey(params) {
|
|
4081
|
+
this.ensureEnabled();
|
|
4082
|
+
return this.amzPost("/ChangePasskey", "TrentService.ChangePasskey", params);
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Sign a message or an EIP-155 transaction (WebAuthn-gated).
|
|
4086
|
+
* Provide exactly one of `Message` (hex) or `Transaction`. For a raw 32-byte
|
|
4087
|
+
* digest use {@link signHash} / {@link signHashWithWebAuthn} instead.
|
|
4088
|
+
*/
|
|
4089
|
+
async sign(params) {
|
|
4090
|
+
this.ensureEnabled();
|
|
4091
|
+
return this.amzPost("/Sign", "TrentService.Sign", params);
|
|
4092
|
+
}
|
|
4093
|
+
/**
|
|
4094
|
+
* Poll KeyStatus until the key is ready (address derived) or timeout.
|
|
4095
|
+
* STM32 key derivation takes 60-75 seconds on first creation.
|
|
4096
|
+
*/
|
|
4097
|
+
async pollUntilReady(keyId, timeoutMs = 12e4, intervalMs = 3e3) {
|
|
4098
|
+
this.ensureEnabled();
|
|
4099
|
+
const deadline = Date.now() + timeoutMs;
|
|
4100
|
+
while (Date.now() < deadline) {
|
|
4101
|
+
const status = await this.getKeyStatus(keyId);
|
|
4102
|
+
this.logger.debug(`Key ${keyId} status: ${status.Status}`);
|
|
4103
|
+
if (status.Status === "ready") {
|
|
4104
|
+
return status;
|
|
4105
|
+
}
|
|
4106
|
+
if (status.Status === "error") {
|
|
4107
|
+
throw new Error(`KMS key derivation failed: ${status.Error ?? "unknown error"}`);
|
|
4108
|
+
}
|
|
4109
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
4110
|
+
}
|
|
4111
|
+
throw new Error(`KMS key derivation timed out after ${timeoutMs}ms`);
|
|
4112
|
+
}
|
|
4113
|
+
// ── Signing ─────────────────────────────────────────────────────
|
|
4114
|
+
/**
|
|
4115
|
+
* Sign a hash using Legacy Passkey assertion (reusable for BLS dual-signing).
|
|
4116
|
+
*/
|
|
4117
|
+
async signHash(hash, assertion, target) {
|
|
4118
|
+
this.ensureEnabled();
|
|
4119
|
+
const formattedHash = hash.startsWith("0x") ? hash : `0x${hash}`;
|
|
4120
|
+
const body = {
|
|
4121
|
+
Hash: formattedHash,
|
|
4122
|
+
Passkey: assertion
|
|
4123
|
+
};
|
|
4124
|
+
if (target.Address) {
|
|
4125
|
+
body.Address = target.Address;
|
|
4126
|
+
}
|
|
4127
|
+
if (target.KeyId) {
|
|
4128
|
+
body.KeyId = target.KeyId;
|
|
4129
|
+
}
|
|
4130
|
+
return this.amzPost("/SignHash", "TrentService.SignHash", body);
|
|
4131
|
+
}
|
|
4132
|
+
/**
|
|
4133
|
+
* Sign a hash using a WebAuthn ceremony assertion (one-time use).
|
|
4134
|
+
*/
|
|
4135
|
+
async signHashWithWebAuthn(hash, challengeId, credential, target) {
|
|
4136
|
+
this.ensureEnabled();
|
|
4137
|
+
const formattedHash = hash.startsWith("0x") ? hash : `0x${hash}`;
|
|
4138
|
+
const body = {
|
|
4139
|
+
Hash: formattedHash,
|
|
4140
|
+
WebAuthn: { ChallengeId: challengeId, Credential: credential }
|
|
4141
|
+
};
|
|
4142
|
+
if (target.Address) {
|
|
4143
|
+
body.Address = target.Address;
|
|
4144
|
+
}
|
|
4145
|
+
if (target.KeyId) {
|
|
4146
|
+
body.KeyId = target.KeyId;
|
|
4147
|
+
}
|
|
4148
|
+
return this.amzPost("/SignHash", "TrentService.SignHash", body);
|
|
4149
|
+
}
|
|
4150
|
+
// ── Sign Typed Data (v0.19.0+) ─────────────────────────────────
|
|
4151
|
+
/**
|
|
4152
|
+
* Sign arbitrary EIP-712 typed data via `POST /kms/SignTypedData` (v0.20.0).
|
|
4153
|
+
*
|
|
4154
|
+
* The KMS hashes the typed data host-side, so the FULL EIP-712 structure
|
|
4155
|
+
* (domain / primaryType / types / message) is sent — not a pre-hashed
|
|
4156
|
+
* domainSeparator/structHash. The `webAuthnAssertion` challenge comes from a
|
|
4157
|
+
* generic {@link beginAuthentication} ceremony (purpose="authentication").
|
|
4158
|
+
*
|
|
4159
|
+
* Alternatively, agents authenticate with a Bearer JWT — see KmsAgentService.
|
|
4160
|
+
*/
|
|
4161
|
+
async signTypedDataWithWebAuthn(params) {
|
|
4162
|
+
this.ensureEnabled();
|
|
4163
|
+
return this.client.post("/kms/SignTypedData", params);
|
|
4164
|
+
}
|
|
4165
|
+
// ── Grant Session Off-chain Signing (v0.19.0+) ─────────────────
|
|
4166
|
+
/**
|
|
4167
|
+
* Begin a grant-session WebAuthn challenge.
|
|
4168
|
+
* The returned challengeId can ONLY be used with sign-grant-session, not sign-typed-data.
|
|
4169
|
+
*/
|
|
4170
|
+
async beginGrantSessionAuth(params) {
|
|
4171
|
+
this.ensureEnabled();
|
|
4172
|
+
return this.client.get("/kms/begin-grant-session-auth", {
|
|
4173
|
+
params: { keyId: params.keyId }
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
/**
|
|
4177
|
+
* Sign a GRANT_SESSION_V2 hash off-chain inside the TEE (secp256k1 session key).
|
|
4178
|
+
* Returns a 65-byte signature (R||S||V, V=27/28) for use in grantSessionWithSig().
|
|
4179
|
+
*/
|
|
4180
|
+
async signGrantSession(params) {
|
|
4181
|
+
this.ensureEnabled();
|
|
4182
|
+
return this.client.post("/kms/sign-grant-session", params);
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* Sign a GRANT_P256_SESSION_V2 hash off-chain inside the TEE (P256 session key).
|
|
4186
|
+
* Returns a 65-byte signature for use in grantP256SessionWithSig().
|
|
4187
|
+
*/
|
|
4188
|
+
async signP256GrantSession(params) {
|
|
4189
|
+
this.ensureEnabled();
|
|
4190
|
+
return this.client.post("/kms/sign-p256-grant-session", params);
|
|
4191
|
+
}
|
|
4192
|
+
// ── Challenge-binding ceremonies (#49 / Beta3) ──────────────────
|
|
4193
|
+
//
|
|
4194
|
+
// These run the full WebAuthn challenge-binding ceremony in one call:
|
|
4195
|
+
// fetch the TA one-time nonce, embed it in clientDataJSON, build + sign the
|
|
4196
|
+
// assertion, then invoke the signing endpoint with the resulting
|
|
4197
|
+
// `WebAuthn` / `webAuthnAssertion`. They share the
|
|
4198
|
+
// {@link runAuthenticationCeremony} / {@link runGrantSessionCeremony} helper,
|
|
4199
|
+
// so every path produces an identical, replay-protected assertion structure.
|
|
4200
|
+
/**
|
|
4201
|
+
* Run a generic authentication ceremony (purpose="authentication") bound to a
|
|
4202
|
+
* fresh TA challenge. The returned assertion is valid for DeriveAddress / Sign
|
|
4203
|
+
* / SignHash / SignTypedData / agent-key / p256-session signing.
|
|
4204
|
+
*/
|
|
4205
|
+
async runAuthenticationCeremony(keyId, signer, options) {
|
|
4206
|
+
this.ensureEnabled();
|
|
4207
|
+
return runAuthenticationCeremony(this.client, keyId, signer, options);
|
|
4208
|
+
}
|
|
4209
|
+
/**
|
|
4210
|
+
* Run a grant-session ceremony (purpose="grant-session") bound to a fresh TA
|
|
4211
|
+
* challenge — required by {@link signGrantSession} / {@link signP256GrantSession}
|
|
4212
|
+
* (the generic 'authentication' challenge is rejected there for replay safety).
|
|
4213
|
+
*/
|
|
4214
|
+
async runGrantSessionCeremony(keyId, signer, options) {
|
|
4215
|
+
this.ensureEnabled();
|
|
4216
|
+
return runGrantSessionCeremony(this.client, keyId, signer, options);
|
|
4217
|
+
}
|
|
4218
|
+
/** Derive an address, running the challenge-binding ceremony internally. */
|
|
4219
|
+
async deriveAddressWithCeremony(params, signer, options) {
|
|
4220
|
+
this.ensureEnabled();
|
|
4221
|
+
const WebAuthn = await this.runAuthenticationCeremony(params.KeyId, signer, options);
|
|
4222
|
+
return this.deriveAddress({ ...params, WebAuthn });
|
|
4223
|
+
}
|
|
4224
|
+
/**
|
|
4225
|
+
* Sign a message or EIP-155 transaction, running the challenge-binding ceremony
|
|
4226
|
+
* internally. `params.KeyId` is required (it identifies the wallet to challenge).
|
|
4227
|
+
*/
|
|
4228
|
+
async signWithCeremony(params, signer, options) {
|
|
4229
|
+
this.ensureEnabled();
|
|
4230
|
+
const WebAuthn = await this.runAuthenticationCeremony(params.KeyId, signer, options);
|
|
4231
|
+
return this.sign({ ...params, WebAuthn });
|
|
4232
|
+
}
|
|
4233
|
+
/** Sign a 32-byte digest, running the challenge-binding ceremony internally. */
|
|
4234
|
+
async signHashWithCeremony(hash, target, signer, options) {
|
|
4235
|
+
this.ensureEnabled();
|
|
4236
|
+
const assertion = await this.runAuthenticationCeremony(target.KeyId, signer, options);
|
|
4237
|
+
return this.signHashWithWebAuthn(hash, assertion.ChallengeId, assertion.Credential, target);
|
|
4238
|
+
}
|
|
4239
|
+
/** Sign EIP-712 typed data, running the challenge-binding ceremony internally. */
|
|
4240
|
+
async signTypedDataWithCeremony(params, signer, options) {
|
|
4241
|
+
this.ensureEnabled();
|
|
4242
|
+
const webAuthnAssertion = await this.runAuthenticationCeremony(params.keyId, signer, options);
|
|
4243
|
+
return this.signTypedDataWithWebAuthn({ ...params, webAuthnAssertion });
|
|
4244
|
+
}
|
|
4245
|
+
/**
|
|
4246
|
+
* Sign a GRANT_SESSION_V2 hash, running the grant-session ceremony internally
|
|
4247
|
+
* (uses the purpose-bound `begin-grant-session-auth` challenge).
|
|
4248
|
+
*/
|
|
4249
|
+
async signGrantSessionWithCeremony(params, signer, options) {
|
|
4250
|
+
this.ensureEnabled();
|
|
4251
|
+
const webAuthnAssertion = await this.runGrantSessionCeremony(params.keyId, signer, options);
|
|
4252
|
+
return this.signGrantSession({ ...params, webAuthnAssertion });
|
|
4253
|
+
}
|
|
4254
|
+
/**
|
|
4255
|
+
* Sign a GRANT_P256_SESSION_V2 hash, running the grant-session ceremony
|
|
4256
|
+
* internally (uses the purpose-bound `begin-grant-session-auth` challenge).
|
|
4257
|
+
*/
|
|
4258
|
+
async signP256GrantSessionWithCeremony(params, signer, options) {
|
|
4259
|
+
this.ensureEnabled();
|
|
4260
|
+
const webAuthnAssertion = await this.runGrantSessionCeremony(params.keyId, signer, options);
|
|
4261
|
+
return this.signP256GrantSession({ ...params, webAuthnAssertion });
|
|
4262
|
+
}
|
|
4263
|
+
// ── WebAuthn Ceremonies ─────────────────────────────────────────
|
|
4264
|
+
async beginRegistration(params) {
|
|
4265
|
+
this.ensureEnabled();
|
|
4266
|
+
return this.client.post("/BeginRegistration", params);
|
|
4267
|
+
}
|
|
4268
|
+
async completeRegistration(params) {
|
|
4269
|
+
this.ensureEnabled();
|
|
4270
|
+
return this.client.post("/CompleteRegistration", params);
|
|
4271
|
+
}
|
|
4272
|
+
async beginAuthentication(params) {
|
|
4273
|
+
this.ensureEnabled();
|
|
4274
|
+
return this.client.post("/BeginAuthentication", params);
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Begin a generic WebAuthn authentication ceremony for a key, returning a
|
|
4278
|
+
* challenge usable for SignHash / SignTypedData (purpose="authentication").
|
|
4279
|
+
*
|
|
4280
|
+
* NOTE: there is no dedicated `begin-webauthn-auth` endpoint — this delegates
|
|
4281
|
+
* to `POST /BeginAuthentication`. (Grant-session signing needs a purpose-bound
|
|
4282
|
+
* challenge from {@link beginGrantSessionAuth} instead.)
|
|
4283
|
+
*/
|
|
4284
|
+
async beginWebAuthnAuth(keyId) {
|
|
4285
|
+
this.ensureEnabled();
|
|
4286
|
+
return this.client.post("/BeginAuthentication", { KeyId: keyId });
|
|
4287
|
+
}
|
|
4288
|
+
// ── Factory ─────────────────────────────────────────────────────
|
|
4289
|
+
createKmsSigner(keyId, address, assertionProvider) {
|
|
4290
|
+
this.ensureEnabled();
|
|
4291
|
+
return new KmsSigner(keyId, address, this, assertionProvider);
|
|
4292
|
+
}
|
|
4293
|
+
};
|
|
4294
|
+
var KmsSigner = class {
|
|
4295
|
+
constructor(keyId, _address, kmsManager, assertionProvider) {
|
|
4296
|
+
this.keyId = keyId;
|
|
4297
|
+
this._address = _address;
|
|
4298
|
+
this.kmsManager = kmsManager;
|
|
4299
|
+
this.assertionProvider = assertionProvider;
|
|
4300
|
+
}
|
|
4301
|
+
async getAddress() {
|
|
4302
|
+
return this._address;
|
|
4303
|
+
}
|
|
4304
|
+
async signMessage(message) {
|
|
4305
|
+
const messageHash = hashMessage(message);
|
|
4306
|
+
const assertion = await this.assertionProvider();
|
|
4307
|
+
const signResponse = await this.kmsManager.signHash(messageHash, assertion, {
|
|
4308
|
+
Address: this._address
|
|
4309
|
+
});
|
|
4310
|
+
return "0x" + signResponse.Signature;
|
|
4311
|
+
}
|
|
4312
|
+
};
|
|
4313
|
+
|
|
4314
|
+
// ../airaccount/src/server/services/kms-agent-service.ts
|
|
4315
|
+
var KmsAgentService = class {
|
|
4316
|
+
constructor(http2) {
|
|
4317
|
+
this.http = http2;
|
|
4318
|
+
}
|
|
4319
|
+
/**
|
|
4320
|
+
* Mint a new agent key under an existing human key (WebAuthn-gated).
|
|
4321
|
+
*
|
|
4322
|
+
* The WebAuthn challenge is obtained from a generic
|
|
4323
|
+
* {@link KmsManager.beginAuthentication} ceremony (purpose="authentication");
|
|
4324
|
+
* the caller supplies the resulting assertion in the request.
|
|
4325
|
+
*/
|
|
4326
|
+
async createAgentKey(params) {
|
|
4327
|
+
this.http.ensureEnabled();
|
|
4328
|
+
return this.http.post("/kms/create-agent-key", params);
|
|
4329
|
+
}
|
|
4330
|
+
/**
|
|
4331
|
+
* Sign a userOpHash with an agent key, authenticated by the agent's TEE-JWT
|
|
4332
|
+
* credential (`jwt`, the `agentCredential` from {@link createAgentKey}).
|
|
4333
|
+
* Returns the 106-byte packed signature for ERC-4337 sponsorship.
|
|
4334
|
+
*/
|
|
4335
|
+
async signAgent(params, jwt) {
|
|
4336
|
+
this.http.ensureEnabled();
|
|
4337
|
+
return this.http.postWithBearer("/kms/sign-agent", params, jwt);
|
|
4338
|
+
}
|
|
4339
|
+
/**
|
|
4340
|
+
* Refresh (re-mint) an agent credential before it expires. Authenticated with
|
|
4341
|
+
* the existing credential (`jwt`, Bearer) plus a human WebAuthn / passkey
|
|
4342
|
+
* assertion in the request.
|
|
4343
|
+
*/
|
|
4344
|
+
async refreshAgentCredential(params, jwt) {
|
|
4345
|
+
this.http.ensureEnabled();
|
|
4346
|
+
return this.http.postWithBearer(
|
|
4347
|
+
"/kms/refresh-agent-credential",
|
|
4348
|
+
params,
|
|
4349
|
+
jwt
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
/**
|
|
4353
|
+
* Revoke an agent's credential (WebAuthn-gated).
|
|
4354
|
+
*
|
|
4355
|
+
* The WebAuthn challenge is obtained from a generic
|
|
4356
|
+
* {@link KmsManager.beginAuthentication} ceremony (purpose="authentication");
|
|
4357
|
+
* the caller supplies the resulting assertion in the request.
|
|
4358
|
+
*/
|
|
4359
|
+
async revokeAgentCredential(params) {
|
|
4360
|
+
this.http.ensureEnabled();
|
|
4361
|
+
return this.http.post(
|
|
4362
|
+
"/kms/revoke-agent-credential",
|
|
4363
|
+
params
|
|
4364
|
+
);
|
|
4365
|
+
}
|
|
4366
|
+
// ── Challenge-binding ceremony variants (#49 / Beta3) ────────────
|
|
4367
|
+
//
|
|
4368
|
+
// All agent-key WebAuthn gates use the generic purpose="authentication"
|
|
4369
|
+
// challenge bound to the HUMAN key. These helpers run the full ceremony
|
|
4370
|
+
// (begin → clientDataJSON → assertion) via the shared
|
|
4371
|
+
// {@link runAuthenticationCeremony} helper, then invoke the endpoint.
|
|
4372
|
+
/** Mint an agent key, running the challenge-binding ceremony internally. */
|
|
4373
|
+
async createAgentKeyWithCeremony(params, signer, options) {
|
|
4374
|
+
this.http.ensureEnabled();
|
|
4375
|
+
const webAuthnAssertion = await runAuthenticationCeremony(
|
|
4376
|
+
this.http,
|
|
4377
|
+
params.humanKeyId,
|
|
4378
|
+
signer,
|
|
4379
|
+
options
|
|
4380
|
+
);
|
|
4381
|
+
return this.createAgentKey({ ...params, webAuthnAssertion });
|
|
4382
|
+
}
|
|
4383
|
+
/**
|
|
4384
|
+
* Refresh an agent credential, running the challenge-binding ceremony
|
|
4385
|
+
* internally. `humanKeyId` is the owning human key challenged by the ceremony
|
|
4386
|
+
* (distinct from the agent `keyId` in `params`); `jwt` is the existing credential.
|
|
4387
|
+
*/
|
|
4388
|
+
async refreshAgentCredentialWithCeremony(params, humanKeyId, jwt, signer, options) {
|
|
4389
|
+
this.http.ensureEnabled();
|
|
4390
|
+
const webAuthnAssertion = await runAuthenticationCeremony(this.http, humanKeyId, signer, options);
|
|
4391
|
+
return this.refreshAgentCredential({ ...params, webAuthnAssertion }, jwt);
|
|
4392
|
+
}
|
|
4393
|
+
/**
|
|
4394
|
+
* Revoke an agent credential, running the challenge-binding ceremony internally.
|
|
4395
|
+
* `humanKeyId` is the owning human key challenged by the ceremony (distinct from
|
|
4396
|
+
* the agent `keyId` in `params`).
|
|
4397
|
+
*/
|
|
4398
|
+
async revokeAgentCredentialWithCeremony(params, humanKeyId, signer, options) {
|
|
4399
|
+
this.http.ensureEnabled();
|
|
4400
|
+
const webAuthnAssertion = await runAuthenticationCeremony(this.http, humanKeyId, signer, options);
|
|
4401
|
+
return this.revokeAgentCredential({ ...params, webAuthnAssertion });
|
|
4402
|
+
}
|
|
4403
|
+
};
|
|
4404
|
+
|
|
4405
|
+
// ../airaccount/src/server/services/kms-session-service.ts
|
|
4406
|
+
var KmsSessionService = class {
|
|
4407
|
+
constructor(http2) {
|
|
4408
|
+
this.http = http2;
|
|
4409
|
+
}
|
|
4410
|
+
/**
|
|
4411
|
+
* Create a P-256 session key under a human key (WebAuthn-gated).
|
|
4412
|
+
*
|
|
4413
|
+
* `POST /kms/create-p256-session-key`. The `webAuthnAssertion` challenge comes
|
|
4414
|
+
* from a generic {@link KmsManager.beginAuthentication} ceremony supplied by
|
|
4415
|
+
* the caller. Returns the session key's public key plus an `agentCredential`
|
|
4416
|
+
* JWT used to authenticate subsequent {@link signP256UserOp} calls.
|
|
4417
|
+
*/
|
|
4418
|
+
async createP256SessionKey(params) {
|
|
4419
|
+
this.http.ensureEnabled();
|
|
4420
|
+
return this.http.post(
|
|
4421
|
+
"/kms/create-p256-session-key",
|
|
4422
|
+
params
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
/**
|
|
4426
|
+
* Sign an ERC-4337 UserOp hash with a P-256 session key (Bearer JWT auth).
|
|
4427
|
+
*
|
|
4428
|
+
* `POST /kms/sign-p256-user-op`, authenticated with the `agentCredential` JWT
|
|
4429
|
+
* returned by {@link createP256SessionKey}. Returns the 149-byte P256
|
|
4430
|
+
* session-key wire-format signature.
|
|
4431
|
+
*/
|
|
4432
|
+
async signP256UserOp(params, jwt) {
|
|
4433
|
+
this.http.ensureEnabled();
|
|
4434
|
+
return this.http.postWithBearer(
|
|
4435
|
+
"/kms/sign-p256-user-op",
|
|
4436
|
+
params,
|
|
4437
|
+
jwt
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
/**
|
|
4441
|
+
* Revoke a P-256 session key (WebAuthn-gated, idempotent).
|
|
4442
|
+
*
|
|
4443
|
+
* `POST /kms/revoke-p256-session-key`. The `webAuthnAssertion` challenge comes
|
|
4444
|
+
* from a generic {@link KmsManager.beginAuthentication} ceremony supplied by
|
|
4445
|
+
* the caller. Idempotent: revoking an already-revoked key still resolves.
|
|
4446
|
+
*/
|
|
4447
|
+
async revokeP256SessionKey(params) {
|
|
4448
|
+
this.http.ensureEnabled();
|
|
4449
|
+
return this.http.post(
|
|
4450
|
+
"/kms/revoke-p256-session-key",
|
|
4451
|
+
params
|
|
4452
|
+
);
|
|
4453
|
+
}
|
|
4454
|
+
// ── Challenge-binding ceremony variants (#49 / Beta3) ────────────
|
|
4455
|
+
//
|
|
4456
|
+
// Create + revoke gate on the generic purpose="authentication" challenge bound
|
|
4457
|
+
// to the HUMAN key. These helpers run the full ceremony (begin → clientDataJSON
|
|
4458
|
+
// → assertion) via the shared {@link runAuthenticationCeremony} helper.
|
|
4459
|
+
/** Create a P-256 session key, running the challenge-binding ceremony internally. */
|
|
4460
|
+
async createP256SessionKeyWithCeremony(params, signer, options) {
|
|
4461
|
+
this.http.ensureEnabled();
|
|
4462
|
+
const webAuthnAssertion = await runAuthenticationCeremony(
|
|
4463
|
+
this.http,
|
|
4464
|
+
params.humanKeyId,
|
|
4465
|
+
signer,
|
|
4466
|
+
options
|
|
4467
|
+
);
|
|
4468
|
+
return this.createP256SessionKey({ ...params, webAuthnAssertion });
|
|
4469
|
+
}
|
|
4470
|
+
/**
|
|
4471
|
+
* Revoke a P-256 session key, running the challenge-binding ceremony internally.
|
|
4472
|
+
* `humanKeyId` is the owning human key challenged by the ceremony (distinct from
|
|
4473
|
+
* the session `keyId` in `params`).
|
|
4474
|
+
*/
|
|
4475
|
+
async revokeP256SessionKeyWithCeremony(params, humanKeyId, signer, options) {
|
|
4476
|
+
this.http.ensureEnabled();
|
|
4477
|
+
const webAuthnAssertion = await runAuthenticationCeremony(this.http, humanKeyId, signer, options);
|
|
4478
|
+
return this.revokeP256SessionKey({ ...params, webAuthnAssertion });
|
|
4479
|
+
}
|
|
4480
|
+
};
|
|
4481
|
+
|
|
4482
|
+
// ../airaccount/src/server/services/kms-payment-signer.ts
|
|
4483
|
+
var KmsPaymentSigner = class {
|
|
4484
|
+
constructor(http2) {
|
|
4485
|
+
this.http = http2;
|
|
4486
|
+
}
|
|
4487
|
+
/**
|
|
4488
|
+
* Dispatch a payment-signing request with the chosen auth mode.
|
|
4489
|
+
* JWT auth uses `postWithBearer`; WebAuthn auth merges the assertion into the body.
|
|
4490
|
+
*/
|
|
4491
|
+
async signWithAuth(path, body, auth) {
|
|
4492
|
+
if ("jwt" in auth) {
|
|
4493
|
+
return this.http.postWithBearer(path, body, auth.jwt);
|
|
4494
|
+
}
|
|
4495
|
+
return this.http.post(path, {
|
|
4496
|
+
...body,
|
|
4497
|
+
webAuthnAssertion: auth.webAuthnAssertion
|
|
4498
|
+
});
|
|
4499
|
+
}
|
|
4500
|
+
/**
|
|
4501
|
+
* Sign a MicroPaymentChannel voucher (cumulative-amount EIP-712 message)
|
|
4502
|
+
* via `POST /kms/SignMicropaymentVoucher`.
|
|
4503
|
+
*/
|
|
4504
|
+
async signMicropaymentVoucher(params, auth) {
|
|
4505
|
+
this.http.ensureEnabled();
|
|
4506
|
+
return this.signWithAuth("/kms/SignMicropaymentVoucher", { ...params }, auth);
|
|
4507
|
+
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Sign an EIP-3009 TransferWithAuthorization for a GToken transfer
|
|
4510
|
+
* via `POST /kms/SignGTokenAuthorization`. `from` MUST equal the derived address.
|
|
4511
|
+
*/
|
|
4512
|
+
async signGTokenAuthorization(params, auth) {
|
|
4513
|
+
this.http.ensureEnabled();
|
|
4514
|
+
return this.signWithAuth("/kms/SignGTokenAuthorization", { ...params }, auth);
|
|
4515
|
+
}
|
|
4516
|
+
/**
|
|
4517
|
+
* Sign an x402 payment authorization via `POST /kms/SignX402Payment`.
|
|
4518
|
+
*/
|
|
4519
|
+
async signX402Payment(params, auth) {
|
|
4520
|
+
this.http.ensureEnabled();
|
|
4521
|
+
return this.signWithAuth("/kms/SignX402Payment", { ...params }, auth);
|
|
4522
|
+
}
|
|
4523
|
+
};
|
|
4524
|
+
|
|
4525
|
+
// ../airaccount/src/server/services/kms-monitor-service.ts
|
|
4526
|
+
var KmsMonitorService = class {
|
|
4527
|
+
constructor(http2) {
|
|
4528
|
+
this.http = http2;
|
|
4529
|
+
}
|
|
4530
|
+
/**
|
|
4531
|
+
* Liveness probe (`GET /health`, no auth). Does NOT require the KMS feature
|
|
4532
|
+
* flag to be enabled.
|
|
4533
|
+
*/
|
|
4534
|
+
async health() {
|
|
4535
|
+
return this.http.get("/health");
|
|
4536
|
+
}
|
|
4537
|
+
/**
|
|
4538
|
+
* Version / capability descriptor (`GET /version`, no auth). Does NOT require
|
|
4539
|
+
* the KMS feature flag to be enabled.
|
|
4540
|
+
*/
|
|
4541
|
+
async version() {
|
|
4542
|
+
return this.http.get("/version");
|
|
4543
|
+
}
|
|
4544
|
+
/**
|
|
4545
|
+
* Request-queue health and circuit-breaker state (`GET /QueueStatus`).
|
|
4546
|
+
*/
|
|
4547
|
+
async queueStatus() {
|
|
4548
|
+
this.http.ensureEnabled();
|
|
4549
|
+
return this.http.get("/QueueStatus");
|
|
4550
|
+
}
|
|
4551
|
+
/**
|
|
4552
|
+
* RPMB anti-rollback monotonic counter (`GET /RollbackCounter`, diagnostic,
|
|
4553
|
+
* v0.20.0).
|
|
4554
|
+
*/
|
|
4555
|
+
async rollbackCounter() {
|
|
4556
|
+
this.http.ensureEnabled();
|
|
4557
|
+
return this.http.get("/RollbackCounter");
|
|
4558
|
+
}
|
|
4559
|
+
/**
|
|
4560
|
+
* Machine-readable runtime statistics (`GET /stats`, v0.20.0) — wallets, tx,
|
|
4561
|
+
* queue, warnings.
|
|
4562
|
+
*/
|
|
4563
|
+
async stats() {
|
|
4564
|
+
this.http.ensureEnabled();
|
|
4565
|
+
return this.http.get("/stats");
|
|
4566
|
+
}
|
|
4567
|
+
/**
|
|
4568
|
+
* TEE remote-attestation evidence bound to a caller nonce (`GET /attestation`,
|
|
4569
|
+
* #37). Public (no auth) — pass a fresh random `nonce` (hex, ≤64 bytes) to bind
|
|
4570
|
+
* the evidence + defeat replay, then verify the returned signed measurement.
|
|
4571
|
+
*/
|
|
4572
|
+
async getAttestation(nonce) {
|
|
4573
|
+
return this.http.get("/attestation", { params: { nonce } });
|
|
4574
|
+
}
|
|
4575
|
+
/**
|
|
4576
|
+
* Ed25519-signed measurement manifest, version → ta_measurement
|
|
4577
|
+
* (`GET /.well-known/attestation-measurements.json`, #12). Public.
|
|
4578
|
+
*/
|
|
4579
|
+
async getAttestationMeasurements() {
|
|
4580
|
+
return this.http.get("/.well-known/attestation-measurements.json");
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Sigsum transparency proof sidecar for the measurement manifest
|
|
4584
|
+
* (`GET /.well-known/attestation-measurements-proof.json`, #87). Public.
|
|
4585
|
+
*/
|
|
4586
|
+
async getAttestationMeasurementsProof() {
|
|
4587
|
+
return this.http.get("/.well-known/attestation-measurements-proof.json");
|
|
4588
|
+
}
|
|
4589
|
+
/**
|
|
4590
|
+
* WARNING — DESTRUCTIVE, IRREVERSIBLE. Force-purges a key from both the TEE
|
|
4591
|
+
* and the SQLite store with NO passkey/WebAuthn check (`POST /admin/purge-key`,
|
|
4592
|
+
* v0.20.0). Operator-only: authorised solely by the `KMS_ADMIN_TOKEN` operator
|
|
4593
|
+
* secret sent as `Authorization: Bearer <adminToken>`. There is no recovery
|
|
4594
|
+
* once a key is purged.
|
|
4595
|
+
*
|
|
4596
|
+
* @internal Operator/break-glass tooling only — not part of the general SDK surface.
|
|
4597
|
+
* The endpoint is gated server-side and intentionally omitted from the public KMS
|
|
4598
|
+
* docs; do not expose it in application-facing flows.
|
|
4599
|
+
*/
|
|
4600
|
+
async adminPurgeKey(params, adminToken) {
|
|
4601
|
+
this.http.ensureEnabled();
|
|
4602
|
+
return this.http.postWithBearer("/admin/purge-key", params, adminToken);
|
|
4603
|
+
}
|
|
4604
|
+
};
|
|
4605
|
+
|
|
4606
|
+
// ../airaccount/src/server/adapters/memory-storage.ts
|
|
4607
|
+
var MemoryStorage = class {
|
|
4608
|
+
accounts = [];
|
|
4609
|
+
transfers = [];
|
|
4610
|
+
paymasters = /* @__PURE__ */ new Map();
|
|
4611
|
+
blsConfig = null;
|
|
4612
|
+
// ── Accounts ─────────────────────────────────────────────────
|
|
4613
|
+
async getAccounts() {
|
|
4614
|
+
return [...this.accounts];
|
|
4615
|
+
}
|
|
4616
|
+
async saveAccount(account) {
|
|
4617
|
+
this.accounts.push({ ...account });
|
|
4618
|
+
}
|
|
4619
|
+
async findAccountByUserId(userId) {
|
|
4620
|
+
return this.accounts.find((a) => a.userId === userId) ?? null;
|
|
4621
|
+
}
|
|
4622
|
+
async updateAccount(userId, updates) {
|
|
4623
|
+
const index = this.accounts.findIndex((a) => a.userId === userId);
|
|
4624
|
+
if (index >= 0) {
|
|
4625
|
+
this.accounts[index] = { ...this.accounts[index], ...updates };
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
// ── Transfers ────────────────────────────────────────────────
|
|
4629
|
+
async saveTransfer(transfer) {
|
|
4630
|
+
this.transfers.push({ ...transfer });
|
|
4631
|
+
}
|
|
4632
|
+
async findTransfersByUserId(userId) {
|
|
4633
|
+
return this.transfers.filter((t) => t.userId === userId);
|
|
4634
|
+
}
|
|
4635
|
+
async findTransferById(id) {
|
|
4636
|
+
return this.transfers.find((t) => t.id === id) ?? null;
|
|
4637
|
+
}
|
|
4638
|
+
async updateTransfer(id, updates) {
|
|
4639
|
+
const index = this.transfers.findIndex((t) => t.id === id);
|
|
4640
|
+
if (index >= 0) {
|
|
4641
|
+
this.transfers[index] = { ...this.transfers[index], ...updates };
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
// ── Paymasters ───────────────────────────────────────────────
|
|
4645
|
+
async getPaymasters(userId) {
|
|
4646
|
+
return this.paymasters.get(userId) ?? [];
|
|
4647
|
+
}
|
|
4648
|
+
async savePaymaster(userId, paymaster) {
|
|
4649
|
+
const list = this.paymasters.get(userId) ?? [];
|
|
4650
|
+
const existingIndex = list.findIndex((p) => p.name === paymaster.name);
|
|
4651
|
+
if (existingIndex >= 0) {
|
|
4652
|
+
list[existingIndex] = { ...paymaster };
|
|
4653
|
+
} else {
|
|
4654
|
+
list.push({ ...paymaster });
|
|
4655
|
+
}
|
|
4656
|
+
this.paymasters.set(userId, list);
|
|
4657
|
+
}
|
|
4658
|
+
async removePaymaster(userId, name) {
|
|
4659
|
+
const list = this.paymasters.get(userId) ?? [];
|
|
4660
|
+
const filtered = list.filter((p) => p.name !== name);
|
|
4661
|
+
if (filtered.length < list.length) {
|
|
4662
|
+
this.paymasters.set(userId, filtered);
|
|
4663
|
+
return true;
|
|
4664
|
+
}
|
|
4665
|
+
return false;
|
|
4666
|
+
}
|
|
4667
|
+
// ── BLS Config ───────────────────────────────────────────────
|
|
4668
|
+
async getBlsConfig() {
|
|
4669
|
+
return this.blsConfig;
|
|
4670
|
+
}
|
|
4671
|
+
async updateSignerNodesCache(nodes) {
|
|
4672
|
+
this.blsConfig = {
|
|
4673
|
+
...this.blsConfig,
|
|
4674
|
+
signerNodes: {
|
|
4675
|
+
nodes
|
|
4676
|
+
}
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
};
|
|
4680
|
+
var LocalWalletSigner = class {
|
|
4681
|
+
account;
|
|
4682
|
+
constructor(privateKey) {
|
|
4683
|
+
this.account = accounts.privateKeyToAccount(privateKey);
|
|
4684
|
+
}
|
|
4685
|
+
async getAddress(_userId) {
|
|
4686
|
+
return this.account.address;
|
|
4687
|
+
}
|
|
4688
|
+
async signMessage(_userId, message, _ctx) {
|
|
4689
|
+
return this.account.signMessage({ message: { raw: message } });
|
|
4690
|
+
}
|
|
4691
|
+
async ensureSigner(_userId) {
|
|
4692
|
+
return { address: this.account.address };
|
|
4693
|
+
}
|
|
4694
|
+
};
|
|
4695
|
+
/*! Bundled license information:
|
|
4696
|
+
|
|
4697
|
+
@noble/curves/nist.js:
|
|
4698
|
+
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
4699
|
+
*/
|
|
4700
|
+
|
|
4701
|
+
exports.ACCOUNT_ABI = ACCOUNT_ABI;
|
|
4702
|
+
exports.AGENT_SESSION_KEY_VALIDATOR_ABI = AGENT_SESSION_KEY_VALIDATOR_ABI;
|
|
4703
|
+
exports.AIRACCOUNT_ABI = AIRACCOUNT_ABI;
|
|
4704
|
+
exports.AIRACCOUNT_ADDRESSES = AIRACCOUNT_ADDRESSES;
|
|
4705
|
+
exports.AIRACCOUNT_FACTORY_ABI = AIRACCOUNT_FACTORY_ABI;
|
|
4706
|
+
exports.AIR_ACCOUNT_COMPOSITE_VALIDATOR_ABI = AIR_ACCOUNT_COMPOSITE_VALIDATOR_ABI;
|
|
4707
|
+
exports.AIR_ACCOUNT_DELEGATE_ABI = AIR_ACCOUNT_DELEGATE_ABI;
|
|
4708
|
+
exports.AIR_ACCOUNT_DELEGATE_ADDRESS = AIR_ACCOUNT_DELEGATE_ADDRESS;
|
|
4709
|
+
exports.ALG_ID = ALG_ID;
|
|
4710
|
+
exports.AccountManager = AccountManager;
|
|
4711
|
+
exports.AgentRegistryService = AgentRegistryService;
|
|
4712
|
+
exports.AirAccountServerClient = AirAccountServerClient;
|
|
4713
|
+
exports.BLSSignatureService = BLSSignatureService;
|
|
4714
|
+
exports.CALLDATA_PARSER_REGISTRY_ABI = CALLDATA_PARSER_REGISTRY_ABI;
|
|
4715
|
+
exports.ConsoleLogger = ConsoleLogger;
|
|
4716
|
+
exports.DEFAULT_CREDENTIAL_ID = DEFAULT_CREDENTIAL_ID;
|
|
4717
|
+
exports.DEFAULT_KMS_ENDPOINT = DEFAULT_KMS_ENDPOINT;
|
|
4718
|
+
exports.DEFAULT_ORIGIN = DEFAULT_ORIGIN;
|
|
4719
|
+
exports.DEFAULT_RP_ID = DEFAULT_RP_ID;
|
|
4720
|
+
exports.DvtPendingConfirmationError = DvtPendingConfirmationError;
|
|
4721
|
+
exports.EIP7702DelegateService = EIP7702DelegateService;
|
|
4722
|
+
exports.ENTRYPOINT_ABI_V6 = ENTRYPOINT_ABI_V6;
|
|
4723
|
+
exports.ENTRYPOINT_ABI_V7_V8 = ENTRYPOINT_ABI_V7_V8;
|
|
4724
|
+
exports.ENTRYPOINT_ADDRESSES = ENTRYPOINT_ADDRESSES;
|
|
4725
|
+
exports.ERC20_ABI = ERC20_ABI;
|
|
4726
|
+
exports.ERC8004Service = ERC8004Service;
|
|
4727
|
+
exports.ERC8004_ADDRESSES = ERC8004_ADDRESSES;
|
|
4728
|
+
exports.EXECUTE_BATCH_SELECTOR = EXECUTE_BATCH_SELECTOR;
|
|
4729
|
+
exports.EXECUTE_SELECTOR = EXECUTE_SELECTOR;
|
|
4730
|
+
exports.EXECUTE_USER_OP_SELECTOR = EXECUTE_USER_OP_SELECTOR;
|
|
4731
|
+
exports.EntryPointVersion = EntryPointVersion;
|
|
4732
|
+
exports.EthereumProvider = EthereumProvider;
|
|
4733
|
+
exports.FACTORY_ABI_V6 = FACTORY_ABI_V6;
|
|
4734
|
+
exports.FACTORY_ABI_V7_V8 = FACTORY_ABI_V7_V8;
|
|
4735
|
+
exports.FORCE_EXIT_MODULE_ABI = FORCE_EXIT_MODULE_ABI;
|
|
4736
|
+
exports.ForceExitService = ForceExitService;
|
|
4737
|
+
exports.GLOBAL_GUARD_ABI = GLOBAL_GUARD_ABI;
|
|
4738
|
+
exports.GuardChecker = GuardChecker;
|
|
4739
|
+
exports.GuardStateReader = GuardStateReader;
|
|
4740
|
+
exports.KmsAgentService = KmsAgentService;
|
|
4741
|
+
exports.KmsHttpClient = KmsHttpClient;
|
|
4742
|
+
exports.KmsManager = KmsManager;
|
|
4743
|
+
exports.KmsMonitorService = KmsMonitorService;
|
|
4744
|
+
exports.KmsPaymentSigner = KmsPaymentSigner;
|
|
4745
|
+
exports.KmsSessionService = KmsSessionService;
|
|
4746
|
+
exports.KmsSigner = KmsSigner;
|
|
4747
|
+
exports.L2_TYPE = L2_TYPE;
|
|
4748
|
+
exports.LocalWalletSigner = LocalWalletSigner;
|
|
4749
|
+
exports.MAX_GUARDIANS = MAX_GUARDIANS;
|
|
4750
|
+
exports.MODULE_TYPE = MODULE_TYPE;
|
|
4751
|
+
exports.MemoryStorage = MemoryStorage;
|
|
4752
|
+
exports.ModuleManager = ModuleManager;
|
|
4753
|
+
exports.P256PasskeySigner = P256PasskeySigner;
|
|
4754
|
+
exports.PaymasterManager = PaymasterManager;
|
|
4755
|
+
exports.PaymasterPriceStalenessError = PaymasterPriceStalenessError;
|
|
4756
|
+
exports.RECOVERY_THRESHOLD = RECOVERY_THRESHOLD;
|
|
4757
|
+
exports.RECOVERY_TIMELOCK_SECONDS = RECOVERY_TIMELOCK_SECONDS;
|
|
4758
|
+
exports.RecoveryService = RecoveryService;
|
|
4759
|
+
exports.SESSION_KEY_VALIDATOR_ABI = SESSION_KEY_VALIDATOR_ABI;
|
|
4760
|
+
exports.SessionKeyService = SessionKeyService;
|
|
4761
|
+
exports.SilentLogger = SilentLogger;
|
|
4762
|
+
exports.TIER_GUARD_HOOK_ABI = TIER_GUARD_HOOK_ABI;
|
|
4763
|
+
exports.TokenService = TokenService;
|
|
4764
|
+
exports.TransferManager = TransferManager;
|
|
4765
|
+
exports.VALIDATOR_ABI = VALIDATOR_ABI;
|
|
4766
|
+
exports.WEIGHT_CHANGE_EXPIRY_SECONDS = WEIGHT_CHANGE_EXPIRY_SECONDS;
|
|
4767
|
+
exports.WEIGHT_CHANGE_THRESHOLD = WEIGHT_CHANGE_THRESHOLD;
|
|
4768
|
+
exports.WEIGHT_CHANGE_TIMELOCK_SECONDS = WEIGHT_CHANGE_TIMELOCK_SECONDS;
|
|
4769
|
+
exports.WalletManager = WalletManager;
|
|
4770
|
+
exports.WeightedSignatureService = WeightedSignatureService;
|
|
4771
|
+
exports.YAAAServerClient = YAAAServerClient;
|
|
4772
|
+
exports.base64UrlDecode = base64UrlDecode;
|
|
4773
|
+
exports.base64UrlEncode = base64UrlEncode;
|
|
4774
|
+
exports.beginAuthenticationChallenge = beginAuthenticationChallenge;
|
|
4775
|
+
exports.beginGrantSessionChallenge = beginGrantSessionChallenge;
|
|
4776
|
+
exports.buildAuthenticationCredential = buildAuthenticationCredential;
|
|
4777
|
+
exports.buildAuthenticatorData = buildAuthenticatorData;
|
|
4778
|
+
exports.buildClientDataJSON = buildClientDataJSON;
|
|
4779
|
+
exports.buildInstallModuleHash = buildInstallModuleHash;
|
|
4780
|
+
exports.buildUninstallModuleHash = buildUninstallModuleHash;
|
|
4781
|
+
exports.computeOapdSalt = computeOapdSalt;
|
|
4782
|
+
exports.erc8004AddressesForChain = erc8004AddressesForChain;
|
|
4783
|
+
exports.getOapdAddress = getOapdAddress;
|
|
4784
|
+
exports.getOapdAddressWithChainId = getOapdAddressWithChainId;
|
|
4785
|
+
exports.isExecuteUserOpWrapped = isExecuteUserOpWrapped;
|
|
4786
|
+
exports.isOapdDeployed = isOapdDeployed;
|
|
4787
|
+
exports.isPendingConfirmation = isPendingConfirmation;
|
|
4788
|
+
exports.packP256SessionSignature = packP256SessionSignature;
|
|
4789
|
+
exports.packSecp256k1SessionSignature = packSecp256k1SessionSignature;
|
|
4790
|
+
exports.runAuthenticationCeremony = runAuthenticationCeremony;
|
|
4791
|
+
exports.runGrantSessionCeremony = runGrantSessionCeremony;
|
|
4792
|
+
exports.runWebAuthnCeremony = runWebAuthnCeremony;
|
|
4793
|
+
exports.sepoliaV07Config = sepoliaV07Config;
|
|
4794
|
+
exports.validateConfig = validateConfig;
|
|
4795
|
+
exports.wrapExecuteUserOp = wrapExecuteUserOp;
|
|
4796
|
+
//# sourceMappingURL=chunk-OVNOSAL3.cjs.map
|
|
4797
|
+
//# sourceMappingURL=chunk-OVNOSAL3.cjs.map
|