diff --git a/CHANGELOG.md b/CHANGELOG.md index 126c2e48..fab607f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ Release verification (image digests, attestation, governance) is covered in the ## Unreleased +### Security +- `registerWalletDerivation` now enforces a global first-owner binding + (`pkpId → master account`): a wallet address can only ever be registered by + the account that first registered it, closing a cross-account PKP hijack via + publicly visible derivation paths (#575). `getWalletDerivation` fails closed + on the same binding, neutralizing hijack registrations that already happened + before the upgrade. Wallets registered before the upgrade have no binding yet + and remain resolvable (and thus still exploitable) until a one-time on-chain + backfill (`backfillPkpOwners`) runs — operators should run it promptly after + the facet upgrade to close that window. + ### Changed - **All API error responses are now JSON** (`{error, message, fix, docs_url}`) instead of HTML error pages. See the diff --git a/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/AppStorage.sol b/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/AppStorage.sol index 3c0cf677..16c35938 100644 --- a/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/AppStorage.sol +++ b/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/AppStorage.sol @@ -119,6 +119,13 @@ library AppStorage { EnumerableSet.StringSet nodeConfigurationKeys; mapping(string => string) nodeConfigurationValues; uint256 serverTriggerValue; + // pkpId (wallet address) => master apiKeyHash that first registered it. + // Global first-owner binding: prevents a different account from registering + // an already-registered pkpId/derivationPath (key derivation is a stateless + // function of the public path). The binding is set on first registration + // (or via backfillPkpOwners) and never cleared — only the first owner may + // ever (re-)register the address. + mapping(address => uint256) pkpIdToOwnerMaster; } function getStorage() diff --git a/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/ViewsFacet.sol b/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/ViewsFacet.sol index 0f04a81c..5bf79db7 100644 --- a/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/ViewsFacet.sol +++ b/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/ViewsFacet.sol @@ -160,7 +160,36 @@ contract ViewsFacet { address walletAddress ) public view returns (uint256) { AppStorage.Account storage account = getReadOnlyAccount(apiKeyHash); - return account.pkpData[walletAddress].id; + uint256 derivation = account.pkpData[walletAddress].id; + if (derivation == 0) { + return 0; + } + // Cross-account hijack defense (#575). This view is the exact read the + // node uses to resolve a derivation path before signing/decrypting, so + // enforce the global first-owner binding HERE, not just at registration: + // if the wallet has an owner and the resolving (master) account is not + // that owner, the local pkpData entry is a stale pre-fix hijack + // registration — fail closed instead of leaking the victim's path. + // owner == 0 means a pre-migration wallet not yet backfilled: fall + // through so signing keeps working until backfillPkpOwners runs. + AppStorage.AccountConfigStorage storage s = AppStorage.getStorage(); + uint256 owner = s.pkpIdToOwnerMaster[walletAddress]; + if (owner != 0) { + uint256 resolvedMaster = s.allApiKeyHashesToMaster[apiKeyHash]; + if (owner != resolvedMaster) { + revert AppStorage.InvalidRequest("PKP owned by another account"); + } + } + return derivation; + } + + /// @notice Return the master apiKeyHash that first registered a pkpId, or 0 if + /// the pkpId has never been bound (pre-migration wallet or never registered). + /// @dev The binding is never cleared once set; used to audit the global + /// first-owner rule and the one-time backfill migration. + function getPkpOwnerMaster(address pkpId) public view returns (uint256) { + AppStorage.AccountConfigStorage storage s = AppStorage.getStorage(); + return s.pkpIdToOwnerMaster[pkpId]; } function listApiKeys( diff --git a/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/WritesFacet.sol b/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/WritesFacet.sol index 81385cf3..b17fa50a 100644 --- a/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/WritesFacet.sol +++ b/lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/WritesFacet.sol @@ -64,6 +64,10 @@ contract WritesFacet { address indexed pkpId, uint256 derivationPath ); + event PkpOwnerBackfilled( + address indexed pkpId, + uint256 indexed masterHash + ); event UsageApiKeyRemoved( uint256 indexed accountApiKeyHash, uint256 indexed usageApiKeyHash @@ -677,6 +681,19 @@ contract WritesFacet { if (account.pkpData[pkpId].id != 0) { revert AppStorage.InvalidRequest("PKP already registered"); } + // Global first-owner binding. Derivation paths are public on-chain and + // the key is a stateless function of the path, so without this check a + // different account could register an already-registered pkpId under + // its own account and drive the node to sign with the victim's key. + // The first master account to register a pkpId owns it forever. The + // binding is never cleared, so if a future release adds wallet deletion + // only the original owner will be able to re-register the address. + uint256 existingOwner = s.pkpIdToOwnerMaster[pkpId]; + if (existingOwner == 0) { + s.pkpIdToOwnerMaster[pkpId] = masterHash; + } else if (existingOwner != masterHash) { + revert AppStorage.InvalidRequest("PKP owned by another account"); + } account.pkpData[pkpId].id = derivationPath; account.pkpData[pkpId].name = name; account.pkpData[pkpId].description = description; @@ -687,6 +704,35 @@ contract WritesFacet { emit WalletDerivationRegistered(masterHash, pkpId, derivationPath); } + /// @notice One-time migration helper: bind wallets registered before the global + /// owner binding existed to their original master account. + /// @dev Pairs should be derived off-chain from the EARLIEST + /// `WalletDerivationRegistered(masterHash, pkpId, ...)` event per pkpId + /// (first registration wins, matching the rule `registerWalletDerivation` + /// now enforces). Already-bound pkpIds are skipped, never re-assigned, so + /// the call is idempotent and safe to run in batches / re-run. Restricted + /// to the diamond owner or config operator. + function backfillPkpOwners( + address[] calldata pkpIds, + uint256[] calldata masterHashes + ) public { + SecurityLib.revertIfNotConfigOperatorOrOwner(msg.sender); + if (pkpIds.length != masterHashes.length) { + revert AppStorage.InvalidRequest("array length mismatch"); + } + AppStorage.AccountConfigStorage storage s = AppStorage.getStorage(); + for (uint256 i = 0; i < pkpIds.length; i++) { + if (masterHashes[i] == 0) { + revert AppStorage.InvalidRequest("masterHash must be non-zero"); + } + if (s.pkpIdToOwnerMaster[pkpIds[i]] != 0) { + continue; // already bound — never re-assign ownership + } + s.pkpIdToOwnerMaster[pkpIds[i]] = masterHashes[i]; + emit PkpOwnerBackfilled(pkpIds[i], masterHashes[i]); + } + } + function setNodeConfiguration( string memory key, string memory value diff --git a/lit-api-server/blockchain/lit_node_express/hardhat.config.ts b/lit-api-server/blockchain/lit_node_express/hardhat.config.ts index c6bc586c..a42910af 100644 --- a/lit-api-server/blockchain/lit_node_express/hardhat.config.ts +++ b/lit-api-server/blockchain/lit_node_express/hardhat.config.ts @@ -11,6 +11,7 @@ import "./tasks/propose-add-device"; import "./tasks/transfer-ownership"; import "./tasks/verify-compose-hash"; import "./tasks/verify-diamond-facets"; +import "./tasks/backfill-pkp-owners"; const config: HardhatUserConfig = { solidity: { diff --git a/lit-api-server/blockchain/lit_node_express/tasks/backfill-pkp-owners.ts b/lit-api-server/blockchain/lit_node_express/tasks/backfill-pkp-owners.ts new file mode 100644 index 00000000..c2b3c318 --- /dev/null +++ b/lit-api-server/blockchain/lit_node_express/tasks/backfill-pkp-owners.ts @@ -0,0 +1,243 @@ +import { task } from "hardhat/config"; +import { ethers } from "ethers"; + +// Minimal ABI: the event we scan plus the migration entry points. +const DIAMOND_ABI = [ + "event WalletDerivationRegistered(uint256 indexed apiKeyHash, address indexed pkpId, uint256 derivationPath)", + "function backfillPkpOwners(address[] pkpIds, uint256[] masterHashes)", + "function getPkpOwnerMaster(address pkpId) view returns (uint256)", +]; + +interface FirstRegistration { + pkpId: string; + masterHash: bigint; + blockNumber: number; + txHash: string; + conflicts: bigint[]; // other masters that later registered the same pkpId +} + +/** + * One-time migration for issue #575: wallets registered before the global + * `pkpIdToOwnerMaster` binding existed have no owner entry, so any account + * could still claim them via registerWalletDerivation. This task rebuilds the + * binding from history: it scans every WalletDerivationRegistered event, takes + * the FIRST registration per pkpId (the same rule the contract now enforces), + * and submits backfillPkpOwners in batches. Already-bound pkpIds are skipped + * on-chain, so the task is idempotent and safe to re-run until it reports + * nothing left to bind. + */ +task( + "backfill-pkp-owners", + "Backfill pkpIdToOwnerMaster for wallets registered before the #575 fix" +) + .addParam("diamond", "Diamond proxy contract address") + .addOptionalParam("fromBlock", "Block to start scanning events from", "0") + .addOptionalParam("chunkSize", "getLogs block range per request", "10000") + .addOptionalParam("batchSize", "pkpIds per backfill transaction", "200") + .addOptionalParam( + "confirmations", + "Blocks to stay behind chain head to avoid reorgs (0 = use the 'finalized' tag)", + "0" + ) + .addFlag("execute", "Send the backfill transactions (default is dry-run)") + .addFlag( + "allowConflicts", + "Proceed even if pkpIds were registered by multiple accounts (pre-fix hijacks). Off by default: conflicts are a hard stop under --execute." + ) + .setAction(async (taskArgs, hre) => { + const { diamond: diamondAddress } = taskArgs; + const fromBlock = parseInt(taskArgs.fromBlock, 10); + const chunkSize = parseInt(taskArgs.chunkSize, 10); + const batchSize = parseInt(taskArgs.batchSize, 10); + const confirmations = parseInt(taskArgs.confirmations, 10); + + const rpcUrl = + (hre.network.config as { url?: string }).url || "https://mainnet.base.org"; + const provider = new ethers.JsonRpcProvider(rpcUrl); + const readOnly = new ethers.Contract(diamondAddress, DIAMOND_ABI, provider); + + console.log(`Network: ${hre.network.name}`); + console.log(`Diamond: ${diamondAddress}`); + + // 1. Scan all WalletDerivationRegistered events. The event's first indexed + // arg is the master apiKeyHash (WritesFacet emits masterHash, never a + // usage-key hash), so it is exactly the value pkpIdToOwnerMaster needs. + // Scan to a finalized/confirmed head, not `latest`: a reorg that reranks + // the first registrant would otherwise bind the wrong owner. The scanned + // upper bound is fixed up front so the whole run reasons over one range. + let latestBlock: number; + if (confirmations > 0) { + latestBlock = (await provider.getBlockNumber()) - confirmations; + } else { + const finalized = await provider.getBlock("finalized"); + if (!finalized) { + throw new Error( + "Provider does not support the 'finalized' block tag; pass --confirmations N instead" + ); + } + latestBlock = finalized.number; + } + if (latestBlock < fromBlock) { + console.log("No finalized blocks in range yet. Nothing to do."); + return; + } + console.log( + `Scanning events from block ${fromBlock} to ${latestBlock} (finalized head)...` + ); + + const filter = readOnly.filters.WalletDerivationRegistered(); + const allLogs: ethers.EventLog[] = []; + for (let start = fromBlock; start <= latestBlock; start += chunkSize) { + const end = Math.min(start + chunkSize - 1, latestBlock); + const logs = await readOnly.queryFilter(filter, start, end); + for (const log of logs) allLogs.push(log as ethers.EventLog); + if (end < latestBlock) { + process.stdout.write( + `\r scanned up to block ${end} (${allLogs.length} events)` + ); + } + } + + // Sort explicitly by (blockNumber, transactionIndex, logIndex) rather than + // trusting provider ordering — "first registration wins" is only correct if + // we actually process logs in chain order, and getLogs ordering is not + // guaranteed across or within chunks. + allLogs.sort( + (a, b) => + a.blockNumber - b.blockNumber || + a.transactionIndex - b.transactionIndex || + a.index - b.index + ); + + const firstByPkp = new Map(); + for (const log of allLogs) { + const masterHash = log.args[0] as bigint; + const pkpId = (log.args[1] as string).toLowerCase(); + const existing = firstByPkp.get(pkpId); + if (!existing) { + firstByPkp.set(pkpId, { + pkpId: log.args[1] as string, + masterHash, + blockNumber: log.blockNumber, + txHash: log.transactionHash, + conflicts: [], + }); + } else if ( + existing.masterHash !== masterHash && + !existing.conflicts.includes(masterHash) + ) { + existing.conflicts.push(masterHash); + } + } + console.log( + `\nFound ${allLogs.length} registration events across ${firstByPkp.size} distinct pkpIds.` + ); + + // 2. Surface pkpIds registered by more than one master account. Each is a + // wallet another account also claimed pre-fix — a probable hijack. The + // backfill binds the FIRST registrant, and the hardened getWalletDerivation + // then refuses to serve any other account's stale local entry. But a + // conflict still means the operator must verify the first registrant is + // genuinely the rightful owner: an attacker who registered BEFORE the + // victim would be bound as owner here, permanently locking the victim out + // (this release has no removeWalletDerivation, so a wrong binding cannot be + // undone on-chain). The later account's stale pkpData row is left in place + // but is inert — getWalletDerivation fails closed for the non-owner. So + // conflicts are a HARD STOP under --execute unless the operator has + // reviewed them and passes --allow-conflicts. + const conflicted = [...firstByPkp.values()].filter( + (r) => r.conflicts.length > 0 + ); + if (conflicted.length > 0) { + console.log( + `\n⚠️ ${conflicted.length} pkpId(s) were registered by MULTIPLE master accounts (probable pre-fix hijack):` + ); + for (const r of conflicted) { + console.log( + ` ${r.pkpId} first=0x${r.masterHash.toString(16)} (block ${r.blockNumber}, ${r.txHash})` + ); + for (const other of r.conflicts) { + console.log(` also registered by 0x${other.toString(16)}`); + } + } + console.log( + " Backfill binds the FIRST registrant. Verify it is the rightful owner before proceeding: this release has no removeWalletDerivation, so a wrong binding cannot be undone on-chain. Any other account's stale pkpData row is left in place but is inert — getWalletDerivation fails closed for the non-owner." + ); + if (taskArgs.execute && !taskArgs.allowConflicts) { + throw new Error( + `Refusing to --execute with ${conflicted.length} unresolved conflict(s). Confirm the first registrant is the rightful owner for each, then re-run with --allow-conflicts.` + ); + } + } + + // 3. Drop pkpIds that are already bound (post-fix registrations, or a + // previous run of this task). + console.log("\nChecking current on-chain bindings..."); + const toBind: FirstRegistration[] = []; + for (const r of firstByPkp.values()) { + const owner: bigint = await readOnly.getPkpOwnerMaster(r.pkpId); + if (owner === 0n) { + toBind.push(r); + } else if (owner !== r.masterHash) { + console.log( + ` ⚠️ ${r.pkpId} already bound to 0x${owner.toString(16)} which is NOT its first registrant 0x${r.masterHash.toString(16)} — investigate` + ); + } + } + console.log(`${toBind.length} pkpId(s) need backfilling.`); + if (toBind.length === 0) { + console.log("Nothing to do."); + return; + } + + if (!taskArgs.execute) { + console.log("\nDry run (pass --execute to send transactions):"); + for (const r of toBind) { + console.log(` ${r.pkpId} -> 0x${r.masterHash.toString(16)}`); + } + return; + } + + // 4. Send backfillPkpOwners in batches. Caller must be the diamond owner + // or config operator. + const signerKey = + process.env.CONFIG_OPERATOR_PRIVATE_KEY || process.env.OWNER_PRIVATE_KEY; + if (!signerKey) { + throw new Error( + "CONFIG_OPERATOR_PRIVATE_KEY or OWNER_PRIVATE_KEY environment variable is required with --execute" + ); + } + const wallet = new ethers.Wallet(signerKey, provider); + const diamond = new ethers.Contract(diamondAddress, DIAMOND_ABI, wallet); + console.log(`\nSending backfill as ${wallet.address}...`); + + for (let i = 0; i < toBind.length; i += batchSize) { + const batch = toBind.slice(i, i + batchSize); + const tx = await diamond.backfillPkpOwners( + batch.map((r) => r.pkpId), + batch.map((r) => r.masterHash) + ); + console.log( + ` batch ${i / batchSize + 1} (${batch.length} pkpIds): ${tx.hash}` + ); + const receipt = await tx.wait(); + console.log(` confirmed in block ${receipt.blockNumber}`); + } + + // 5. Verify every pair landed. + console.log("\nVerifying..."); + let failures = 0; + for (const r of toBind) { + const owner: bigint = await readOnly.getPkpOwnerMaster(r.pkpId); + if (owner !== r.masterHash) { + failures++; + console.log( + ` ❌ ${r.pkpId}: expected 0x${r.masterHash.toString(16)}, got 0x${owner.toString(16)}` + ); + } + } + if (failures > 0) { + throw new Error(`${failures} binding(s) failed verification`); + } + console.log(`All ${toBind.length} bindings verified. Backfill complete.`); + }); diff --git a/lit-api-server/blockchain/lit_node_express/test/Accounts.t.sol b/lit-api-server/blockchain/lit_node_express/test/Accounts.t.sol index 81caed18..716fbe92 100644 --- a/lit-api-server/blockchain/lit_node_express/test/Accounts.t.sol +++ b/lit-api-server/blockchain/lit_node_express/test/Accounts.t.sol @@ -309,6 +309,153 @@ contract AccountsTest is BaseTest { writes.registerWalletDerivation(hash, pkpAddr, 43, "dup", "dup"); } + function test_registerWalletDerivation_crossAccountHijackReverts() public { + // Victim registers a wallet; its derivationPath is public on-chain. + vm.prank(user); + writes.newChainSecuredAccount("victim", "victim"); + uint256 victimHash = apiKeyHashOf(user); + + address pkpAddr = address(0xBEEF); + vm.prank(user); + writes.registerWalletDerivation(victimHash, pkpAddr, 42, "v", "v"); + assertEq(views_.getPkpOwnerMaster(pkpAddr), victimHash); + + // Attacker with their own account cannot register the victim's pkpId, + // even though the attacker's account has no entry for it. + vm.prank(stranger); + writes.newChainSecuredAccount("attacker", "attacker"); + uint256 attackerHash = apiKeyHashOf(stranger); + + vm.prank(stranger); + vm.expectRevert( + abi.encodeWithSelector( + AppStorage.InvalidRequest.selector, + "PKP owned by another account" + ) + ); + writes.registerWalletDerivation(attackerHash, pkpAddr, 42, "a", "a"); + } + + function test_backfillPkpOwners_bindsLegacyWalletsAndBlocksHijack() public { + vm.prank(user); + writes.newChainSecuredAccount("legacy", "legacy"); + uint256 legacyHash = apiKeyHashOf(user); + vm.prank(stranger); + writes.newChainSecuredAccount("attacker", "attacker"); + uint256 attackerHash = apiKeyHashOf(stranger); + + // Simulate a pre-migration wallet: registered in the account but with no + // global owner binding (as if registered before the upgrade). + address legacyPkp = address(0x1E9AC7); + assertEq(views_.getPkpOwnerMaster(legacyPkp), 0); + + address[] memory pkpIds = new address[](1); + pkpIds[0] = legacyPkp; + uint256[] memory masters = new uint256[](1); + masters[0] = legacyHash; + + // Only the diamond owner or config operator may backfill. + vm.prank(stranger); + vm.expectRevert( + abi.encodeWithSelector( + AppStorage.OnlyConfigOperatorOrOwner.selector, + stranger + ) + ); + writes.backfillPkpOwners(pkpIds, masters); + + vm.prank(owner); + writes.backfillPkpOwners(pkpIds, masters); + assertEq(views_.getPkpOwnerMaster(legacyPkp), legacyHash); + + // Backfill never re-assigns an existing binding (idempotent, skip-if-set). + masters[0] = attackerHash; + vm.prank(owner); + writes.backfillPkpOwners(pkpIds, masters); + assertEq(views_.getPkpOwnerMaster(legacyPkp), legacyHash); + + // Post-backfill, the attacker cannot register the legacy wallet... + vm.prank(stranger); + vm.expectRevert( + abi.encodeWithSelector( + AppStorage.InvalidRequest.selector, + "PKP owned by another account" + ) + ); + writes.registerWalletDerivation(attackerHash, legacyPkp, 7, "a", "a"); + + // ...but the legacy owner can (e.g. #450 recovery re-registration). + vm.prank(user); + writes.registerWalletDerivation(legacyHash, legacyPkp, 7, "l", "l"); + assertEq(views_.getWalletDerivation(legacyHash, legacyPkp), 7); + } + + /// @dev Storage slot of `pkpIdToOwnerMaster[pkpId]`. The mapping is the last + /// field of AccountConfigStorage (field index 18, counting the two + /// EnumerableSet fields as 2 slots each) at base slot + /// keccak256("com.litprotocol.accountconfig.storage"). Used to plant the + /// pre-fix on-chain state that the public API can no longer create. + function _pkpOwnerSlot(address pkpId) internal pure returns (bytes32) { + bytes32 base = keccak256("com.litprotocol.accountconfig.storage"); + bytes32 mapSlot = bytes32(uint256(base) + 18); + return keccak256(abi.encode(pkpId, mapSlot)); + } + + function test_getWalletDerivation_preExistingHijackFailsClosed() public { + // Register under the attacker legitimately: sets owner=attacker AND the + // attacker's account-local pkpData entry (this is the stale row a pre-fix + // hijack would have left behind). + vm.prank(stranger); + writes.newChainSecuredAccount("attacker", "attacker"); + uint256 attackerHash = apiKeyHashOf(stranger); + address pkpAddr = address(0xBEEF); + vm.prank(stranger); + writes.registerWalletDerivation(attackerHash, pkpAddr, 42, "a", "a"); + + // Sanity: verify our slot math matches the contract's own getter before + // we rely on vm.store — guards against a silent storage-layout drift. + assertEq( + uint256(vm.load(address(views_), _pkpOwnerSlot(pkpAddr))), + attackerHash + ); + + // Simulate the post-backfill truth: the VICTIM was the real first owner. + uint256 victimHash = apiKeyHashOf(user); + vm.store(address(views_), _pkpOwnerSlot(pkpAddr), bytes32(victimHash)); + assertEq(views_.getPkpOwnerMaster(pkpAddr), victimHash); + + // The node's signing path calls getWalletDerivation with the caller's + // account hash. The attacker still has a local entry, but the view now + // fails closed because the wallet is owned by another account — this is + // what neutralizes a hijack that already happened before the upgrade. + vm.expectRevert( + abi.encodeWithSelector( + AppStorage.InvalidRequest.selector, + "PKP owned by another account" + ) + ); + views_.getWalletDerivation(attackerHash, pkpAddr); + } + + function test_getWalletDerivation_legacyUnboundStillReadable() public { + // A pre-migration wallet has a local entry but no owner binding yet + // (owner==0). It must keep resolving so signing doesn't break in the + // window between the facet upgrade and the backfill run. + vm.prank(stranger); + writes.newChainSecuredAccount("u", "u"); + uint256 hash = apiKeyHashOf(stranger); + address pkpAddr = address(0xBEEF); + vm.prank(stranger); + writes.registerWalletDerivation(hash, pkpAddr, 42, "a", "a"); + + // Clear the binding to reproduce the not-yet-backfilled legacy state. + vm.store(address(views_), _pkpOwnerSlot(pkpAddr), bytes32(uint256(0))); + assertEq(views_.getPkpOwnerMaster(pkpAddr), 0); + + // owner==0 falls through: the wallet still resolves for its account. + assertEq(views_.getWalletDerivation(hash, pkpAddr), 42); + } + function test_views_unknownAccountReverts() public { // Any read that resolves through `getReadOnlyAccount` should revert for a // hash that has never been written. Using `getAccountWalletAddress` as the diff --git a/lit-api-server/blockchain/lit_node_express/test/helpers/DiamondDeploy.sol b/lit-api-server/blockchain/lit_node_express/test/helpers/DiamondDeploy.sol index 59af2cee..d571e0ba 100644 --- a/lit-api-server/blockchain/lit_node_express/test/helpers/DiamondDeploy.sol +++ b/lit-api-server/blockchain/lit_node_express/test/helpers/DiamondDeploy.sol @@ -107,7 +107,7 @@ library DiamondDeploy { } function viewsSelectors() internal pure returns (bytes4[] memory s) { - s = new bytes4[](34); + s = new bytes4[](35); s[0] = ViewsFacet.adminApiPayerAccount.selector; s[1] = ViewsFacet.api_payers.selector; s[2] = ViewsFacet.pricingOperator.selector; @@ -142,10 +142,11 @@ library DiamondDeploy { s[31] = ViewsFacet.canExecuteActionFast.selector; s[32] = ViewsFacet.canUseWalletInActionFast.selector; s[33] = ViewsFacet.canExecuteActionAndUseWallet.selector; + s[34] = ViewsFacet.getPkpOwnerMaster.selector; } function writesSelectors() internal pure returns (bytes4[] memory s) { - s = new bytes4[](19); + s = new bytes4[](20); s[0] = WritesFacet.newChainSecuredAccount.selector; s[1] = WritesFacet.newAccount.selector; s[2] = WritesFacet.convertToChainSecuredAccount.selector; @@ -165,6 +166,7 @@ library DiamondDeploy { s[16] = WritesFacet.removeUsageApiKey.selector; s[17] = WritesFacet.registerWalletDerivation.selector; s[18] = WritesFacet.setNodeConfiguration.selector; + s[19] = WritesFacet.backfillPkpOwners.selector; } function apiConfigSelectors() internal pure returns (bytes4[] memory s) { diff --git a/lit-api-server/src/accounts/contracts/AccountConfig.json b/lit-api-server/src/accounts/contracts/AccountConfig.json index 71d50979..41256f9e 100644 --- a/lit-api-server/src/accounts/contracts/AccountConfig.json +++ b/lit-api-server/src/accounts/contracts/AccountConfig.json @@ -190,6 +190,17 @@ "name": "OnlyApiPayerOrOwner", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "OnlyConfigOperatorOrOwner", + "type": "error" + }, { "inputs": [ { @@ -466,6 +477,25 @@ "name": "PkpAddedToGroup", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pkpId", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "masterHash", + "type": "uint256" + } + ], + "name": "PkpOwnerBackfilled", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -667,6 +697,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "pkpIds", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "masterHashes", + "type": "uint256[]" + } + ], + "name": "backfillPkpOwners", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1410,6 +1458,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "pkpId", + "type": "address" + } + ], + "name": "getPkpOwnerMaster", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -2061,17 +2128,6 @@ "name": "NotContractOwner", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyConfigOperatorOrOwner", - "type": "error" - }, { "anonymous": false, "inputs": [ diff --git a/lit-api-server/src/accounts/contracts/account_config_contract.rs b/lit-api-server/src/accounts/contracts/account_config_contract.rs index 4ad7b99c..89470ff6 100644 --- a/lit-api-server/src/accounts/contracts/account_config_contract.rs +++ b/lit-api-server/src/accounts/contracts/account_config_contract.rs @@ -1642,6 +1642,7 @@ interface AccountConfig { event GroupRemoved(uint256 indexed apiKeyHash, uint256 indexed groupId); event GroupUpdated(uint256 indexed accountApiKeyHash, uint256 indexed groupId); event PkpAddedToGroup(uint256 indexed apiKeyHash, uint256 indexed groupId, address pkpId); + event PkpOwnerBackfilled(address indexed pkpId, uint256 indexed masterHash); event PkpRemovedFromGroup(uint256 indexed apiKeyHash, uint256 indexed groupId, address pkpId); event PricingOperatorUpdated(address indexed newPricingOperator); event PricingUpdated(uint256 indexed pricingItemId, uint256 price); @@ -1663,6 +1664,7 @@ interface AccountConfig { function apiKeyCanExecuteForAnyGroup(uint256 apiKeyHash, uint256[] memory groupIds) external view returns (bool); function apiPayerCount() external view returns (uint256); function api_payers() external view returns (address[] memory); + function backfillPkpOwners(address[] memory pkpIds, uint256[] memory masterHashes) external; function canExecuteAction(uint256 apiKeyHash, uint256 cidHash) external view returns (bool); function canExecuteActionAndUseWallet(uint256 apiKeyHash, uint256 cidHash, address walletAddress) external view returns (bool canExecute, bool canUseWallet); function canExecuteActionFast(uint256 apiKeyHash, uint256 cidHash) external view returns (bool); @@ -1674,6 +1676,7 @@ interface AccountConfig { function debitApiKey(uint256 apiKeyHash, uint256 amount) external; function getAccountWalletAddress(uint256 apiKeyHash) external view returns (address); function getBillingWalletAddress(uint256 apiKeyHash) external view returns (address); + function getPkpOwnerMaster(address pkpId) external view returns (uint256); function getPricing(uint256 pricingItemId) external view returns (uint256); function getWalletDerivation(uint256 apiKeyHash, address walletAddress) external view returns (uint256); function groupIdsForAction(uint256 apiKeyHash, uint256 cidHash) external view returns (uint256[] memory); @@ -1950,6 +1953,24 @@ interface AccountConfig { ], "stateMutability": "view" }, + { + "type": "function", + "name": "backfillPkpOwners", + "inputs": [ + { + "name": "pkpIds", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "masterHashes", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "canExecuteAction", @@ -2195,6 +2216,25 @@ interface AccountConfig { ], "stateMutability": "view" }, + { + "type": "function", + "name": "getPkpOwnerMaster", + "inputs": [ + { + "name": "pkpId", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getPricing", @@ -3694,6 +3734,25 @@ interface AccountConfig { ], "anonymous": false }, + { + "type": "event", + "name": "PkpOwnerBackfilled", + "inputs": [ + { + "name": "pkpId", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "masterHash", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, { "type": "event", "name": "PkpRemovedFromGroup", @@ -7664,6 +7723,120 @@ pub mod AccountConfig { } }; #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] + /*Event with signature `PkpOwnerBackfilled(address,uint256)` and selector `0x30af16efa92e1e313413b3b542aaad6085038078dfc320d3b4436ace4d48b65b`. + ```solidity + event PkpOwnerBackfilled(address indexed pkpId, uint256 indexed masterHash); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct PkpOwnerBackfilled { + #[allow(missing_docs)] + pub pkpId: alloy::sol_types::private::Address, + #[allow(missing_docs)] + pub masterHash: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for PkpOwnerBackfilled { + type DataTuple<'a> = (); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Address, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "PkpOwnerBackfilled(address,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 48u8, 175u8, 22u8, 239u8, 169u8, 46u8, 30u8, 49u8, 52u8, 19u8, 179u8, 181u8, + 66u8, 170u8, 173u8, 96u8, 133u8, 3u8, 128u8, 120u8, 223u8, 195u8, 32u8, 211u8, + 180u8, 67u8, 106u8, 206u8, 77u8, 72u8, 182u8, 91u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + pkpId: topics.1, + masterHash: topics.2, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + () + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.pkpId.clone(), + self.masterHash.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = ::encode_topic( + &self.pkpId, + ); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.masterHash); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for PkpOwnerBackfilled { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&PkpOwnerBackfilled> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &PkpOwnerBackfilled) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] /*Event with signature `PkpRemovedFromGroup(uint256,uint256,address)` and selector `0x071e211a142d40078c2724eb4fc9fc3bd257912c5a496497605f420355521145`. ```solidity event PkpRemovedFromGroup(uint256 indexed apiKeyHash, uint256 indexed groupId, address pkpId); @@ -10292,6 +10465,157 @@ pub mod AccountConfig { } }; #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] + /*Function with signature `backfillPkpOwners(address[],uint256[])` and selector `0x41275609`. + ```solidity + function backfillPkpOwners(address[] memory pkpIds, uint256[] memory masterHashes) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct backfillPkpOwnersCall { + #[allow(missing_docs)] + pub pkpIds: alloy::sol_types::private::Vec, + #[allow(missing_docs)] + pub masterHashes: + alloy::sol_types::private::Vec, + } + //Container type for the return parameters of the [`backfillPkpOwners(address[],uint256[])`](backfillPkpOwnersCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct backfillPkpOwnersReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + #[allow(dead_code)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Array, + alloy::sol_types::sol_data::Array>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::Vec, + alloy::sol_types::private::Vec< + alloy::sol_types::private::primitives::aliases::U256, + >, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: backfillPkpOwnersCall) -> Self { + (value.pkpIds, value.masterHashes) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for backfillPkpOwnersCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + pkpIds: tuple.0, + masterHashes: tuple.1, + } + } + } + } + { + #[doc(hidden)] + #[allow(dead_code)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: backfillPkpOwnersReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for backfillPkpOwnersReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + impl backfillPkpOwnersReturn { + fn _tokenize( + &self, + ) -> ::ReturnToken<'_> { + () + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for backfillPkpOwnersCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Array, + alloy::sol_types::sol_data::Array>, + ); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = backfillPkpOwnersReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "backfillPkpOwners(address[],uint256[])"; + const SELECTOR: [u8; 4] = [65u8, 39u8, 86u8, 9u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize(&self.pkpIds), + , + > as alloy_sol_types::SolType>::tokenize(&self.masterHashes), + ) + } + #[inline] + fn tokenize_returns(ret: &Self::Return) -> Self::ReturnToken<'_> { + backfillPkpOwnersReturn::_tokenize(ret) + } + #[inline] + fn abi_decode_returns(data: &[u8]) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence(data) + .map(Into::into) + } + #[inline] + fn abi_decode_returns_validate(data: &[u8]) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence_validate( + data, + ) + .map(Into::into) + } + } + }; + #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] /*Function with signature `canExecuteAction(uint256,uint256)` and selector `0xff1fe00c`. ```solidity function canExecuteAction(uint256 apiKeyHash, uint256 cidHash) external view returns (bool); @@ -11953,6 +12277,145 @@ pub mod AccountConfig { } }; #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] + /*Function with signature `getPkpOwnerMaster(address)` and selector `0xb43595c1`. + ```solidity + function getPkpOwnerMaster(address pkpId) external view returns (uint256); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct getPkpOwnerMasterCall { + #[allow(missing_docs)] + pub pkpId: alloy::sol_types::private::Address, + } + #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] + //Container type for the return parameters of the [`getPkpOwnerMaster(address)`](getPkpOwnerMasterCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct getPkpOwnerMasterReturn { + #[allow(missing_docs)] + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + #[allow(dead_code)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Address,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::Address,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: getPkpOwnerMasterCall) -> Self { + (value.pkpId,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for getPkpOwnerMasterCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { pkpId: tuple.0 } + } + } + } + { + #[doc(hidden)] + #[allow(dead_code)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: getPkpOwnerMasterReturn) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for getPkpOwnerMasterReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for getPkpOwnerMasterCall { + type Parameters<'a> = (alloy::sol_types::sol_data::Address,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = alloy::sol_types::private::primitives::aliases::U256; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "getPkpOwnerMaster(address)"; + const SELECTOR: [u8; 4] = [180u8, 53u8, 149u8, 193u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + ::tokenize( + &self.pkpId, + ), + ) + } + #[inline] + fn tokenize_returns(ret: &Self::Return) -> Self::ReturnToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + ret, + ), + ) + } + #[inline] + fn abi_decode_returns(data: &[u8]) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence(data).map( + |r| { + let r: getPkpOwnerMasterReturn = r.into(); + r._0 + }, + ) + } + #[inline] + fn abi_decode_returns_validate(data: &[u8]) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence_validate( + data, + ) + .map(|r| { + let r: getPkpOwnerMasterReturn = r.into(); + r._0 + }) + } + } + }; + #[derive(serde::Serialize, serde::Deserialize, Default, Debug, PartialEq, Eq, Hash)] /*Function with signature `getPricing(uint256)` and selector `0xc12f1a42`. ```solidity function getPricing(uint256 pricingItemId) external view returns (uint256); @@ -18684,6 +19147,8 @@ pub mod AccountConfig { #[allow(missing_docs)] api_payers(api_payersCall), #[allow(missing_docs)] + backfillPkpOwners(backfillPkpOwnersCall), + #[allow(missing_docs)] canExecuteAction(canExecuteActionCall), #[allow(missing_docs)] canExecuteActionAndUseWallet(canExecuteActionAndUseWalletCall), @@ -18706,6 +19171,8 @@ pub mod AccountConfig { #[allow(missing_docs)] getBillingWalletAddress(getBillingWalletAddressCall), #[allow(missing_docs)] + getPkpOwnerMaster(getPkpOwnerMasterCall), + #[allow(missing_docs)] getPricing(getPricingCall), #[allow(missing_docs)] getWalletDerivation(getWalletDerivationCall), @@ -18819,6 +19286,7 @@ pub mod AccountConfig { [56u8, 54u8, 3u8, 254u8], [64u8, 180u8, 212u8, 83u8], [64u8, 212u8, 65u8, 27u8], + [65u8, 39u8, 86u8, 9u8], [74u8, 66u8, 196u8, 10u8], [77u8, 190u8, 191u8, 229u8], [80u8, 64u8, 144u8, 1u8], @@ -18848,6 +19316,7 @@ pub mod AccountConfig { [166u8, 182u8, 182u8, 114u8], [174u8, 140u8, 73u8, 165u8], [178u8, 2u8, 138u8, 151u8], + [180u8, 53u8, 149u8, 193u8], [180u8, 155u8, 139u8, 137u8], [184u8, 3u8, 127u8, 254u8], [192u8, 1u8, 188u8, 121u8], @@ -18887,6 +19356,7 @@ pub mod AccountConfig { ::core::stringify!(adminApiPayerAccount), ::core::stringify!(debitApiKey), ::core::stringify!(removeAction), + ::core::stringify!(backfillPkpOwners), ::core::stringify!(removePkpFromGroup), ::core::stringify!(newChainSecuredAccount), ::core::stringify!(listPkps), @@ -18916,6 +19386,7 @@ pub mod AccountConfig { ::core::stringify!(updateActionMetadata), ::core::stringify!(setApiPayers), ::core::stringify!(canExecuteActionAndUseWallet), + ::core::stringify!(getPkpOwnerMaster), ::core::stringify!(addAction), ::core::stringify!(apiPayerCount), ::core::stringify!(setAdminApiPayerAccount), @@ -18955,6 +19426,7 @@ pub mod AccountConfig { ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, + ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, @@ -18984,6 +19456,7 @@ pub mod AccountConfig { ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, + ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, @@ -19025,7 +19498,7 @@ pub mod AccountConfig { impl alloy_sol_types::SolInterface for AccountConfigCalls { const NAME: &'static str = "AccountConfigCalls"; const MIN_DATA_LENGTH: usize = 0usize; - const COUNT: usize = 65usize; + const COUNT: usize = 67usize; #[inline] fn selector(&self) -> [u8; 4] { match self { @@ -19048,6 +19521,9 @@ pub mod AccountConfig { } Self::apiPayerCount(_) => ::SELECTOR, Self::api_payers(_) => ::SELECTOR, + Self::backfillPkpOwners(_) => { + ::SELECTOR + } Self::canExecuteAction(_) => { ::SELECTOR } @@ -19077,6 +19553,9 @@ pub mod AccountConfig { Self::getBillingWalletAddress(_) => { ::SELECTOR } + Self::getPkpOwnerMaster(_) => { + ::SELECTOR + } Self::getPricing(_) => ::SELECTOR, Self::getWalletDerivation(_) => { ::SELECTOR @@ -19357,6 +19836,15 @@ pub mod AccountConfig { } removeAction }, + { + fn backfillPkpOwners( + data: &[u8], + ) -> alloy_sol_types::Result { + ::abi_decode_raw(data) + .map(AccountConfigCalls::backfillPkpOwners) + } + backfillPkpOwners + }, { fn removePkpFromGroup( data: &[u8], @@ -19628,6 +20116,15 @@ pub mod AccountConfig { } canExecuteActionAndUseWallet }, + { + fn getPkpOwnerMaster( + data: &[u8], + ) -> alloy_sol_types::Result { + ::abi_decode_raw(data) + .map(AccountConfigCalls::getPkpOwnerMaster) + } + getPkpOwnerMaster + }, { fn addAction(data: &[u8]) -> alloy_sol_types::Result { ::abi_decode_raw(data) @@ -19967,6 +20464,17 @@ pub mod AccountConfig { } removeAction }, + { + fn backfillPkpOwners( + data: &[u8], + ) -> alloy_sol_types::Result { + ::abi_decode_raw_validate( + data, + ) + .map(AccountConfigCalls::backfillPkpOwners) + } + backfillPkpOwners + }, { fn removePkpFromGroup( data: &[u8], @@ -20260,6 +20768,17 @@ pub mod AccountConfig { } canExecuteActionAndUseWallet }, + { + fn getPkpOwnerMaster( + data: &[u8], + ) -> alloy_sol_types::Result { + ::abi_decode_raw_validate( + data, + ) + .map(AccountConfigCalls::getPkpOwnerMaster) + } + getPkpOwnerMaster + }, { fn addAction(data: &[u8]) -> alloy_sol_types::Result { ::abi_decode_raw_validate(data) @@ -20474,6 +20993,11 @@ pub mod AccountConfig { Self::api_payers(inner) => { ::abi_encoded_size(inner) } + Self::backfillPkpOwners(inner) => { + ::abi_encoded_size( + inner, + ) + } Self::canExecuteAction(inner) => { ::abi_encoded_size( inner, @@ -20529,6 +21053,11 @@ pub mod AccountConfig { inner, ) } + Self::getPkpOwnerMaster(inner) => { + ::abi_encoded_size( + inner, + ) + } Self::getPricing(inner) => { ::abi_encoded_size(inner) } @@ -20801,6 +21330,12 @@ pub mod AccountConfig { out, ) } + Self::backfillPkpOwners(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } Self::canExecuteAction(inner) => { ::abi_encode_raw( inner, @@ -20867,6 +21402,12 @@ pub mod AccountConfig { out, ) } + Self::getPkpOwnerMaster(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } Self::getPricing(inner) => { ::abi_encode_raw( inner, @@ -22001,6 +22542,8 @@ pub mod AccountConfig { #[allow(missing_docs)] PkpAddedToGroup(PkpAddedToGroup), #[allow(missing_docs)] + PkpOwnerBackfilled(PkpOwnerBackfilled), + #[allow(missing_docs)] PkpRemovedFromGroup(PkpRemovedFromGroup), #[allow(missing_docs)] PricingOperatorUpdated(PricingOperatorUpdated), @@ -22092,6 +22635,11 @@ pub mod AccountConfig { 188u8, 176u8, 95u8, 160u8, 2u8, 71u8, 184u8, 248u8, 24u8, 40u8, 236u8, 4u8, 201u8, 119u8, 55u8, 139u8, 233u8, 237u8, 223u8, 93u8, ], + [ + 48u8, 175u8, 22u8, 239u8, 169u8, 46u8, 30u8, 49u8, 52u8, 19u8, 179u8, 181u8, 66u8, + 170u8, 173u8, 96u8, 133u8, 3u8, 128u8, 120u8, 223u8, 195u8, 32u8, 211u8, 180u8, + 67u8, 106u8, 206u8, 77u8, 72u8, 182u8, 91u8, + ], [ 51u8, 95u8, 90u8, 252u8, 131u8, 254u8, 140u8, 90u8, 1u8, 26u8, 150u8, 220u8, 57u8, 188u8, 206u8, 159u8, 185u8, 212u8, 111u8, 181u8, 152u8, 101u8, 2u8, 247u8, 4u8, @@ -22168,6 +22716,7 @@ pub mod AccountConfig { ::core::stringify!(WalletDerivationRegistered), ::core::stringify!(ConfigOperatorUpdated), ::core::stringify!(GroupRemoved), + ::core::stringify!(PkpOwnerBackfilled), ::core::stringify!(PricingUpdated), ::core::stringify!(GroupUpdated), ::core::stringify!(ApiKeyDebited), @@ -22196,6 +22745,7 @@ pub mod AccountConfig { ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, + ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, ::SIGNATURE, @@ -22231,7 +22781,7 @@ pub mod AccountConfig { #[automatically_derived] impl alloy_sol_types::SolEventInterface for AccountConfigEvents { const NAME: &'static str = "AccountConfigEvents"; - const COUNT: usize = 25usize; + const COUNT: usize = 26usize; fn decode_raw_log( topics: &[alloy_sol_types::Word], data: &[u8], @@ -22361,6 +22911,15 @@ pub mod AccountConfig { ) .map(Self::PkpAddedToGroup) } + Some( + ::SIGNATURE_HASH, + ) => { + ::decode_raw_log( + topics, + data, + ) + .map(Self::PkpOwnerBackfilled) + } Some( ::SIGNATURE_HASH, ) => { @@ -22502,6 +23061,9 @@ pub mod AccountConfig { Self::PkpAddedToGroup(inner) => { alloy_sol_types::private::IntoLogData::to_log_data(inner) } + Self::PkpOwnerBackfilled(inner) => { + alloy_sol_types::private::IntoLogData::to_log_data(inner) + } Self::PkpRemovedFromGroup(inner) => { alloy_sol_types::private::IntoLogData::to_log_data(inner) } @@ -22581,6 +23143,9 @@ pub mod AccountConfig { Self::PkpAddedToGroup(inner) => { alloy_sol_types::private::IntoLogData::into_log_data(inner) } + Self::PkpOwnerBackfilled(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } Self::PkpRemovedFromGroup(inner) => { alloy_sol_types::private::IntoLogData::into_log_data(inner) } @@ -22817,6 +23382,19 @@ pub mod AccountConfig { pub fn api_payers(&self) -> alloy_contract::SolCallBuilder<&P, api_payersCall, N> { self.call_builder(&api_payersCall) } + //Creates a new call builder for the [`backfillPkpOwners`] function. + pub fn backfillPkpOwners( + &self, + pkpIds: alloy::sol_types::private::Vec, + masterHashes: alloy::sol_types::private::Vec< + alloy::sol_types::private::primitives::aliases::U256, + >, + ) -> alloy_contract::SolCallBuilder<&P, backfillPkpOwnersCall, N> { + self.call_builder(&backfillPkpOwnersCall { + pkpIds, + masterHashes, + }) + } //Creates a new call builder for the [`canExecuteAction`] function. pub fn canExecuteAction( &self, @@ -22923,6 +23501,13 @@ pub mod AccountConfig { ) -> alloy_contract::SolCallBuilder<&P, getBillingWalletAddressCall, N> { self.call_builder(&getBillingWalletAddressCall { apiKeyHash }) } + //Creates a new call builder for the [`getPkpOwnerMaster`] function. + pub fn getPkpOwnerMaster( + &self, + pkpId: alloy::sol_types::private::Address, + ) -> alloy_contract::SolCallBuilder<&P, getPkpOwnerMasterCall, N> { + self.call_builder(&getPkpOwnerMasterCall { pkpId }) + } //Creates a new call builder for the [`getPricing`] function. pub fn getPricing( &self, @@ -23500,6 +24085,12 @@ pub mod AccountConfig { pub fn PkpAddedToGroup_filter(&self) -> alloy_contract::Event<&P, PkpAddedToGroup, N> { self.event_filter::() } + //Creates a new event filter for the [`PkpOwnerBackfilled`] event. + pub fn PkpOwnerBackfilled_filter( + &self, + ) -> alloy_contract::Event<&P, PkpOwnerBackfilled, N> { + self.event_filter::() + } //Creates a new event filter for the [`PkpRemovedFromGroup`] event. pub fn PkpRemovedFromGroup_filter( &self,