@agenticprimitives/contracts 1.0.0-alpha.13 → 1.0.0-alpha.15
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/deployments-base-sepolia.json +33 -33
- package/dist/abi/AgentAccount.json +29 -0
- package/dist/abi/AgentAccountFactory.json +11 -0
- package/dist/abi/CustodyPolicy.json +22 -0
- package/dist/abi/DelegationManager.json +11 -0
- package/dist/abi/IAgentAccountFactoryView.json +13 -0
- package/dist/abi/PaymentEnforcer.json +11 -6
- package/dist/deployments/base-sepolia.d.ts +4 -4
- package/dist/deployments/base-sepolia.js +33 -33
- package/package.json +1 -1
- package/src/AgentAccount.sol +173 -9
- package/src/AgentAccountFactory.sol +16 -0
- package/src/SmartAgentPaymaster.sol +11 -6
- package/src/agency/DelegationManager.sol +9 -0
- package/src/custody/CustodyPolicy.sol +38 -0
- package/src/enforcers/PaymentEnforcer.sol +49 -8
- package/src/identity/AgentProfilePredicates.sol +3 -0
- package/src/naming/AgentNamePredicates.sol +21 -0
- package/src/naming/AgentNameRegistry.sol +13 -3
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
"agentAccountFactory": "
|
|
3
|
-
"agentAccountImplementation": "
|
|
4
|
-
"agentNameRegistry": "
|
|
5
|
-
"agentNameResolver": "
|
|
6
|
-
"agentNameUniversalResolver": "
|
|
7
|
-
"agentProfileResolver": "
|
|
8
|
-
"agentRegistryBase": "
|
|
9
|
-
"agentRelationship": "
|
|
10
|
-
"agreementRegistry": "
|
|
11
|
-
"allowedMethodsEnforcer": "
|
|
12
|
-
"allowedTargetsEnforcer": "
|
|
13
|
-
"approvedHashRegistry": "
|
|
14
|
-
"attestationRegistry": "
|
|
15
|
-
"callDataHashEnforcer": "
|
|
2
|
+
"agentAccountFactory": "0x1D3BF769B3991faA7847872cE05FA22beE2f07e5",
|
|
3
|
+
"agentAccountImplementation": "0xd96eA06208140763256899727B22A1DF7A09387d",
|
|
4
|
+
"agentNameRegistry": "0x4e5a6369C91B9093ad17bF0eAc8d344F1b41EdE0",
|
|
5
|
+
"agentNameResolver": "0x776a5bADb7DBE238048542E0c91f7a86933C8986",
|
|
6
|
+
"agentNameUniversalResolver": "0x8539fad5B148512775cD3712bb55F2fd5f1C85cB",
|
|
7
|
+
"agentProfileResolver": "0x82dBC51a77A92A51b177FFE97181cD40aa1c24ab",
|
|
8
|
+
"agentRegistryBase": "0x54f76a5faab995f194cc61c50eb5e272c8d85cf4",
|
|
9
|
+
"agentRelationship": "0x763c164d8275F76A806721B423aaB5ad6cb4fEA1",
|
|
10
|
+
"agreementRegistry": "0xaD300f03EdDf8F015D5583feC8D5F2D1BBC6f854",
|
|
11
|
+
"allowedMethodsEnforcer": "0xa149A31F32789cDBfd5f0eed1CF92E7BCb91b549",
|
|
12
|
+
"allowedTargetsEnforcer": "0xfb78589C8a9f464BD1e9A340BC809CEe7949b17c",
|
|
13
|
+
"approvedHashRegistry": "0x7152282bB71692E7f73ff31DdB43f4a50818845b",
|
|
14
|
+
"attestationRegistry": "0x9f9bCAF7b238b87c6d7897767c473b6f8C55C0a6",
|
|
15
|
+
"callDataHashEnforcer": "0x0fCE11eC016bd4Bc3a60951FbE4e9Fe7087955b1",
|
|
16
16
|
"chainId": 84532,
|
|
17
|
-
"custodyPolicy": "
|
|
18
|
-
"delegationManager": "
|
|
17
|
+
"custodyPolicy": "0xF408e54dA113Fe6b785615750c6Bab6F9008A9d9",
|
|
18
|
+
"delegationManager": "0xaCdB7001dB8305c23703f6DB0EfB8B9BC1d75D62",
|
|
19
19
|
"deployer": "0x31ed17fb99e82E02085Ab4B3cbdaB05489098b44",
|
|
20
|
-
"entryPoint": "
|
|
21
|
-
"geoFeatureRegistry": "
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
20
|
+
"entryPoint": "0x9Eb5F84E8323aACD4AD61b4b980aDB23B15c2903",
|
|
21
|
+
"geoFeatureRegistry": "0x74AEb56420a8630c611c36b3053780d90191f94d",
|
|
22
|
+
"mockUsdc": "0x07190E7586a468E808D6bd58dC3CEdEC0b25B8E3",
|
|
23
|
+
"ontologyTermRegistry": "0x69670d24AcE8C47a294231a1fdC4207642669a25",
|
|
24
|
+
"paymentEnforcer": "0xF08946A2c4eed77f366AfDc39AE150F2262E5d29",
|
|
25
|
+
"paymentEscrow": "0x30f0fd53dc1245062962b498be3a7e9641cc7c6c",
|
|
26
|
+
"paymentReceiptRegistry": "0x36f0cd94A286b011b8C6106E69E14567fb7A498e",
|
|
27
|
+
"permissionlessSubregistry": "0xE37E1da21Bcce70b515d2f44d84D757c358b96B6",
|
|
28
|
+
"permissionlessSubregistryDemoAgent": "0xeE2Db355624BdB4a7551604229EbE72683f8fD85",
|
|
29
|
+
"quorumEnforcer": "0xd030196bD6Ba6a654e6E8f7e3609aAd470122422",
|
|
30
|
+
"relationshipTypeRegistry": "0x8D5f597f2dE53CC8Ed1a948abF972F8C85301a17",
|
|
31
|
+
"shapeRegistry": "0x41273A14e77d7dA256c05276B95D8F2054C54930",
|
|
32
|
+
"skillDefinitionRegistry": "0x9bcd76e8E7e5E57863dca0631F44707974cBa58B",
|
|
33
|
+
"smartAgentPaymaster": "0xb48Ccecb9118D579204868850f62feb4653c1469",
|
|
34
|
+
"timestampEnforcer": "0x5ce38463DcE9c910F09a14e6D0650258e72eEcCA",
|
|
35
|
+
"universalSignatureValidator": "0x4BC3Ea8920c87638e0612007eb9de0d70ee4Ba8f",
|
|
36
|
+
"valueEnforcer": "0x45170540Ba4d90C7c27103FBF4B136c5EdA545BB"
|
|
37
37
|
}
|
|
@@ -590,6 +590,25 @@
|
|
|
590
590
|
],
|
|
591
591
|
"stateMutability": "view"
|
|
592
592
|
},
|
|
593
|
+
{
|
|
594
|
+
"type": "function",
|
|
595
|
+
"name": "isExternalCustodianOrPasskey",
|
|
596
|
+
"inputs": [
|
|
597
|
+
{
|
|
598
|
+
"name": "account",
|
|
599
|
+
"type": "address",
|
|
600
|
+
"internalType": "address"
|
|
601
|
+
}
|
|
602
|
+
],
|
|
603
|
+
"outputs": [
|
|
604
|
+
{
|
|
605
|
+
"name": "",
|
|
606
|
+
"type": "bool",
|
|
607
|
+
"internalType": "bool"
|
|
608
|
+
}
|
|
609
|
+
],
|
|
610
|
+
"stateMutability": "view"
|
|
611
|
+
},
|
|
593
612
|
{
|
|
594
613
|
"type": "function",
|
|
595
614
|
"name": "isModuleInstalled",
|
|
@@ -1267,6 +1286,11 @@
|
|
|
1267
1286
|
}
|
|
1268
1287
|
]
|
|
1269
1288
|
},
|
|
1289
|
+
{
|
|
1290
|
+
"type": "error",
|
|
1291
|
+
"name": "AdminRequiresCustodyQuorum",
|
|
1292
|
+
"inputs": []
|
|
1293
|
+
},
|
|
1270
1294
|
{
|
|
1271
1295
|
"type": "error",
|
|
1272
1296
|
"name": "AgenticPrimitivesAgentNotAllowedAsCustodian",
|
|
@@ -1310,6 +1334,11 @@
|
|
|
1310
1334
|
}
|
|
1311
1335
|
]
|
|
1312
1336
|
},
|
|
1337
|
+
{
|
|
1338
|
+
"type": "error",
|
|
1339
|
+
"name": "DelegatedGovernanceForbidden",
|
|
1340
|
+
"inputs": []
|
|
1341
|
+
},
|
|
1313
1342
|
{
|
|
1314
1343
|
"type": "error",
|
|
1315
1344
|
"name": "DirectUpgradeBlocked",
|
|
@@ -390,6 +390,17 @@
|
|
|
390
390
|
],
|
|
391
391
|
"anonymous": false
|
|
392
392
|
},
|
|
393
|
+
{
|
|
394
|
+
"type": "error",
|
|
395
|
+
"name": "InfeasibleOrgConfig",
|
|
396
|
+
"inputs": [
|
|
397
|
+
{
|
|
398
|
+
"name": "nSigners",
|
|
399
|
+
"type": "uint8",
|
|
400
|
+
"internalType": "uint8"
|
|
401
|
+
}
|
|
402
|
+
]
|
|
403
|
+
},
|
|
393
404
|
{
|
|
394
405
|
"type": "error",
|
|
395
406
|
"name": "InsufficientTrusteesForMode",
|
|
@@ -778,6 +778,17 @@
|
|
|
778
778
|
}
|
|
779
779
|
]
|
|
780
780
|
},
|
|
781
|
+
{
|
|
782
|
+
"type": "error",
|
|
783
|
+
"name": "AdminSelfSignerNotAllowed",
|
|
784
|
+
"inputs": [
|
|
785
|
+
{
|
|
786
|
+
"name": "account",
|
|
787
|
+
"type": "address",
|
|
788
|
+
"internalType": "address"
|
|
789
|
+
}
|
|
790
|
+
]
|
|
791
|
+
},
|
|
781
792
|
{
|
|
782
793
|
"type": "error",
|
|
783
794
|
"name": "AdminUnauthorizedSigner",
|
|
@@ -843,6 +854,17 @@
|
|
|
843
854
|
"name": "EmptyOwnerSet",
|
|
844
855
|
"inputs": []
|
|
845
856
|
},
|
|
857
|
+
{
|
|
858
|
+
"type": "error",
|
|
859
|
+
"name": "InfeasibleOrgConfig",
|
|
860
|
+
"inputs": [
|
|
861
|
+
{
|
|
862
|
+
"name": "nSigners",
|
|
863
|
+
"type": "uint8",
|
|
864
|
+
"internalType": "uint8"
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
},
|
|
846
868
|
{
|
|
847
869
|
"type": "error",
|
|
848
870
|
"name": "InvalidCustodyAction",
|
|
@@ -622,6 +622,17 @@
|
|
|
622
622
|
"name": "LegacyRevocationDisabled",
|
|
623
623
|
"inputs": []
|
|
624
624
|
},
|
|
625
|
+
{
|
|
626
|
+
"type": "error",
|
|
627
|
+
"name": "NoCodeEnforcer",
|
|
628
|
+
"inputs": [
|
|
629
|
+
{
|
|
630
|
+
"name": "enforcer",
|
|
631
|
+
"type": "address",
|
|
632
|
+
"internalType": "address"
|
|
633
|
+
}
|
|
634
|
+
]
|
|
635
|
+
},
|
|
625
636
|
{
|
|
626
637
|
"type": "error",
|
|
627
638
|
"name": "NotDelegatorOrDelegate",
|
|
@@ -12,6 +12,19 @@
|
|
|
12
12
|
],
|
|
13
13
|
"stateMutability": "view"
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
"type": "function",
|
|
17
|
+
"name": "custodyPolicy",
|
|
18
|
+
"inputs": [],
|
|
19
|
+
"outputs": [
|
|
20
|
+
{
|
|
21
|
+
"name": "",
|
|
22
|
+
"type": "address",
|
|
23
|
+
"internalType": "address"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"stateMutability": "view"
|
|
27
|
+
},
|
|
15
28
|
{
|
|
16
29
|
"type": "function",
|
|
17
30
|
"name": "governance",
|
|
@@ -20,22 +20,22 @@
|
|
|
20
20
|
"name": "afterHook",
|
|
21
21
|
"inputs": [
|
|
22
22
|
{
|
|
23
|
-
"name": "",
|
|
23
|
+
"name": "terms",
|
|
24
24
|
"type": "bytes",
|
|
25
25
|
"internalType": "bytes"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
"name": "",
|
|
28
|
+
"name": "args",
|
|
29
29
|
"type": "bytes",
|
|
30
30
|
"internalType": "bytes"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
"name": "",
|
|
33
|
+
"name": "delegationHash",
|
|
34
34
|
"type": "bytes32",
|
|
35
35
|
"internalType": "bytes32"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
|
-
"name": "",
|
|
38
|
+
"name": "delegator",
|
|
39
39
|
"type": "address",
|
|
40
40
|
"internalType": "address"
|
|
41
41
|
},
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"internalType": "uint256"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
|
-
"name": "",
|
|
58
|
+
"name": "callData",
|
|
59
59
|
"type": "bytes",
|
|
60
60
|
"internalType": "bytes"
|
|
61
61
|
}
|
|
62
62
|
],
|
|
63
63
|
"outputs": [],
|
|
64
|
-
"stateMutability": "
|
|
64
|
+
"stateMutability": "nonpayable"
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"type": "function",
|
|
@@ -412,6 +412,11 @@
|
|
|
412
412
|
"name": "NotTransferCall",
|
|
413
413
|
"inputs": []
|
|
414
414
|
},
|
|
415
|
+
{
|
|
416
|
+
"type": "error",
|
|
417
|
+
"name": "SettlementNotObserved",
|
|
418
|
+
"inputs": []
|
|
419
|
+
},
|
|
415
420
|
{
|
|
416
421
|
"type": "error",
|
|
417
422
|
"name": "ValueNotZero",
|
|
@@ -21,7 +21,11 @@ export interface Deployments {
|
|
|
21
21
|
readonly "deployer": Address;
|
|
22
22
|
readonly "entryPoint": Address;
|
|
23
23
|
readonly "geoFeatureRegistry": Address;
|
|
24
|
+
readonly "mockUsdc": Address;
|
|
24
25
|
readonly "ontologyTermRegistry": Address;
|
|
26
|
+
readonly "paymentEnforcer": Address;
|
|
27
|
+
readonly "paymentEscrow": Address;
|
|
28
|
+
readonly "paymentReceiptRegistry": Address;
|
|
25
29
|
readonly "permissionlessSubregistry": Address;
|
|
26
30
|
readonly "permissionlessSubregistryDemoAgent": Address;
|
|
27
31
|
readonly "quorumEnforcer": Address;
|
|
@@ -32,10 +36,6 @@ export interface Deployments {
|
|
|
32
36
|
readonly "timestampEnforcer": Address;
|
|
33
37
|
readonly "universalSignatureValidator": Address;
|
|
34
38
|
readonly "valueEnforcer": Address;
|
|
35
|
-
readonly "paymentEnforcer": Address;
|
|
36
|
-
readonly "paymentReceiptRegistry": Address;
|
|
37
|
-
readonly "mockUsdc": Address;
|
|
38
|
-
readonly "paymentEscrow": Address;
|
|
39
39
|
readonly "chainId": number;
|
|
40
40
|
}
|
|
41
41
|
export declare const CONTRACTS: Deployments;
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
// Generated by scripts/build-deployments.mjs — do not edit by hand.
|
|
2
2
|
// Source: deployments-base-sepolia.json
|
|
3
3
|
export const CONTRACTS = {
|
|
4
|
-
"agentAccountFactory": "
|
|
5
|
-
"agentAccountImplementation": "
|
|
6
|
-
"agentNameRegistry": "
|
|
7
|
-
"agentNameResolver": "
|
|
8
|
-
"agentNameUniversalResolver": "
|
|
9
|
-
"agentProfileResolver": "
|
|
10
|
-
"agentRegistryBase": "
|
|
11
|
-
"agentRelationship": "
|
|
12
|
-
"agreementRegistry": "
|
|
13
|
-
"allowedMethodsEnforcer": "
|
|
14
|
-
"allowedTargetsEnforcer": "
|
|
15
|
-
"approvedHashRegistry": "
|
|
16
|
-
"attestationRegistry": "
|
|
17
|
-
"callDataHashEnforcer": "
|
|
4
|
+
"agentAccountFactory": "0x1D3BF769B3991faA7847872cE05FA22beE2f07e5",
|
|
5
|
+
"agentAccountImplementation": "0xd96eA06208140763256899727B22A1DF7A09387d",
|
|
6
|
+
"agentNameRegistry": "0x4e5a6369C91B9093ad17bF0eAc8d344F1b41EdE0",
|
|
7
|
+
"agentNameResolver": "0x776a5bADb7DBE238048542E0c91f7a86933C8986",
|
|
8
|
+
"agentNameUniversalResolver": "0x8539fad5B148512775cD3712bb55F2fd5f1C85cB",
|
|
9
|
+
"agentProfileResolver": "0x82dBC51a77A92A51b177FFE97181cD40aa1c24ab",
|
|
10
|
+
"agentRegistryBase": "0x54f76a5faab995f194cc61c50eb5e272c8d85cf4",
|
|
11
|
+
"agentRelationship": "0x763c164d8275F76A806721B423aaB5ad6cb4fEA1",
|
|
12
|
+
"agreementRegistry": "0xaD300f03EdDf8F015D5583feC8D5F2D1BBC6f854",
|
|
13
|
+
"allowedMethodsEnforcer": "0xa149A31F32789cDBfd5f0eed1CF92E7BCb91b549",
|
|
14
|
+
"allowedTargetsEnforcer": "0xfb78589C8a9f464BD1e9A340BC809CEe7949b17c",
|
|
15
|
+
"approvedHashRegistry": "0x7152282bB71692E7f73ff31DdB43f4a50818845b",
|
|
16
|
+
"attestationRegistry": "0x9f9bCAF7b238b87c6d7897767c473b6f8C55C0a6",
|
|
17
|
+
"callDataHashEnforcer": "0x0fCE11eC016bd4Bc3a60951FbE4e9Fe7087955b1",
|
|
18
18
|
"chainId": 84532,
|
|
19
|
-
"custodyPolicy": "
|
|
20
|
-
"delegationManager": "
|
|
19
|
+
"custodyPolicy": "0xF408e54dA113Fe6b785615750c6Bab6F9008A9d9",
|
|
20
|
+
"delegationManager": "0xaCdB7001dB8305c23703f6DB0EfB8B9BC1d75D62",
|
|
21
21
|
"deployer": "0x31ed17fb99e82E02085Ab4B3cbdaB05489098b44",
|
|
22
|
-
"entryPoint": "
|
|
23
|
-
"geoFeatureRegistry": "
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
22
|
+
"entryPoint": "0x9Eb5F84E8323aACD4AD61b4b980aDB23B15c2903",
|
|
23
|
+
"geoFeatureRegistry": "0x74AEb56420a8630c611c36b3053780d90191f94d",
|
|
24
|
+
"mockUsdc": "0x07190E7586a468E808D6bd58dC3CEdEC0b25B8E3",
|
|
25
|
+
"ontologyTermRegistry": "0x69670d24AcE8C47a294231a1fdC4207642669a25",
|
|
26
|
+
"paymentEnforcer": "0xF08946A2c4eed77f366AfDc39AE150F2262E5d29",
|
|
27
|
+
"paymentEscrow": "0x30f0fd53dc1245062962b498be3a7e9641cc7c6c",
|
|
28
|
+
"paymentReceiptRegistry": "0x36f0cd94A286b011b8C6106E69E14567fb7A498e",
|
|
29
|
+
"permissionlessSubregistry": "0xE37E1da21Bcce70b515d2f44d84D757c358b96B6",
|
|
30
|
+
"permissionlessSubregistryDemoAgent": "0xeE2Db355624BdB4a7551604229EbE72683f8fD85",
|
|
31
|
+
"quorumEnforcer": "0xd030196bD6Ba6a654e6E8f7e3609aAd470122422",
|
|
32
|
+
"relationshipTypeRegistry": "0x8D5f597f2dE53CC8Ed1a948abF972F8C85301a17",
|
|
33
|
+
"shapeRegistry": "0x41273A14e77d7dA256c05276B95D8F2054C54930",
|
|
34
|
+
"skillDefinitionRegistry": "0x9bcd76e8E7e5E57863dca0631F44707974cBa58B",
|
|
35
|
+
"smartAgentPaymaster": "0xb48Ccecb9118D579204868850f62feb4653c1469",
|
|
36
|
+
"timestampEnforcer": "0x5ce38463DcE9c910F09a14e6D0650258e72eEcCA",
|
|
37
|
+
"universalSignatureValidator": "0x4BC3Ea8920c87638e0612007eb9de0d70ee4Ba8f",
|
|
38
|
+
"valueEnforcer": "0x45170540Ba4d90C7c27103FBF4B136c5EdA545BB"
|
|
39
39
|
};
|
|
40
40
|
export default CONTRACTS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticprimitives/contracts",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.15",
|
|
4
4
|
"description": "Solidity contracts + ABIs + flattened sources + per-network deployment addresses for the agenticprimitives stack.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/src/AgentAccount.sol
CHANGED
|
@@ -28,6 +28,11 @@ interface IAgentAccountFactoryView {
|
|
|
28
28
|
/// this address to call `IGovernanceView.isPaused()` from
|
|
29
29
|
/// `whenNotPaused`-guarded entrypoints.
|
|
30
30
|
function governance() external view returns (address);
|
|
31
|
+
/// @dev Wave-1/ACCT-C1 — the canonical CustodyPolicy this factory installs
|
|
32
|
+
/// as the custody executor for `mode > 0` accounts. AgentAccount reads it
|
|
33
|
+
/// to decide whether IT is custody-governed (its CustodyPolicy is installed)
|
|
34
|
+
/// vs. merely hosting an unrelated utility executor.
|
|
35
|
+
function custodyPolicy() external view returns (address);
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
/// @dev R6.5 — minimal interface for the pause read. Mirrors the same
|
|
@@ -158,6 +163,27 @@ contract AgentAccount is
|
|
|
158
163
|
/// `_authorizeUpgrade` frame) AND absent from the storage layout.
|
|
159
164
|
bool private transient _upgradeAuthorizedCtx;
|
|
160
165
|
|
|
166
|
+
/// @dev Wave-1 / ACCT-C1 (spec 300) — transient "this call stack was entered
|
|
167
|
+
/// by the installed CustodyPolicy EXECUTOR via `executeFromModule`"
|
|
168
|
+
/// (custody-quorum authorized) flag. Set ONLY inside `executeFromModule`,
|
|
169
|
+
/// around the dispatched call, cleared after. Governance mutators require
|
|
170
|
+
/// it on a custody-governed account (`mode > 0`, a CustodyPolicy EXECUTOR
|
|
171
|
+
/// installed): a 1-of-N UserOp `execute(self, adminSelector)` self-call
|
|
172
|
+
/// leaves the flag unset and reverts, so the quorum+timelock can no longer
|
|
173
|
+
/// be bypassed by a single compromised key. Person accounts (`mode 0`, no
|
|
174
|
+
/// CustodyPolicy) self-govern and are exempt. Transient ⇒ tx-global +
|
|
175
|
+
/// absent from the storage layout.
|
|
176
|
+
bool private transient _custodyGovCtx;
|
|
177
|
+
|
|
178
|
+
/// @dev Wave-1 / DM-3 (spec 300) — transient "this execution stack originated
|
|
179
|
+
/// from the DelegationManager". Set at the outermost `execute`/`executeBatch`
|
|
180
|
+
/// frame whose `msg.sender == _delegationManager`, preserved across the
|
|
181
|
+
/// legitimate `DM → execute(self, executeBatch(...))` nest, cleared by the
|
|
182
|
+
/// frame that set it. While set, a self-targeted governance selector is
|
|
183
|
+
/// rejected — a delegation may never reach account governance (that is the
|
|
184
|
+
/// CustodyPolicy's job), regardless of custody mode.
|
|
185
|
+
bool private transient _delegationExecCtx;
|
|
186
|
+
|
|
161
187
|
/// @dev Spec 007 Phase A.5 — maximum permitted upgrade timelock to
|
|
162
188
|
/// protect against an owner accidentally setting an absurdly
|
|
163
189
|
/// large delay that bricks future migrations.
|
|
@@ -223,6 +249,15 @@ contract AgentAccount is
|
|
|
223
249
|
error LegacyUpgradePathDisabled();
|
|
224
250
|
error ModuleOperationNotAllowed();
|
|
225
251
|
|
|
252
|
+
// Wave 1 — account authorization boundary (spec 300 / ACCT-C1 + DM-3).
|
|
253
|
+
/// @notice A governance mutator was reached on a custody-governed account
|
|
254
|
+
/// (`mode > 0`) outside the CustodyPolicy `executeFromModule` path —
|
|
255
|
+
/// i.e. a single-signer UserOp `execute(self, adminSelector)` self-call.
|
|
256
|
+
error AdminRequiresCustodyQuorum();
|
|
257
|
+
/// @notice A delegation-originated execution stack tried to self-call an
|
|
258
|
+
/// account-governance selector. Delegations may never reach governance.
|
|
259
|
+
error DelegatedGovernanceForbidden();
|
|
260
|
+
|
|
226
261
|
/// @notice Emitted when `setDelegationManager` rotates the DM via a
|
|
227
262
|
/// self-call (the only authorized path post-Wave-2A).
|
|
228
263
|
event DelegationManagerRotated(address indexed newDelegationManager);
|
|
@@ -441,8 +476,14 @@ contract AgentAccount is
|
|
|
441
476
|
// matured owner queue — set `_upgradeAuthorizedCtx` and pass through, so the
|
|
442
477
|
// custody path is not double-delayed. A zero timelock keeps the
|
|
443
478
|
// immediate-upgrade default (backward-compatible).
|
|
444
|
-
|
|
445
|
-
|
|
479
|
+
// Wave-1/ACCT-C1 + M-4 (spec 300): outside an authorized context, a
|
|
480
|
+
// custody-governed account (`mode>0`) may upgrade ONLY through the
|
|
481
|
+
// CustodyPolicy quorum — reject even when the per-account timelock is 0 (M-4
|
|
482
|
+
// floors it non-zero at the factory, but a quorum-set 0 must not re-open the
|
|
483
|
+
// direct path). A person account keeps the CA-1 behavior (timelock if set).
|
|
484
|
+
if (!_upgradeAuthorizedCtx) {
|
|
485
|
+
if (_hasCustodyGovernance()) revert AdminRequiresCustodyQuorum();
|
|
486
|
+
if (_upgradeTimelock != 0) revert DirectUpgradeBlocked();
|
|
446
487
|
}
|
|
447
488
|
}
|
|
448
489
|
|
|
@@ -478,6 +519,7 @@ contract AgentAccount is
|
|
|
478
519
|
/// meaningful when a timelock is configured — with none set, upgrade
|
|
479
520
|
/// directly. Overwrites any existing pending upgrade.
|
|
480
521
|
function scheduleUpgrade(address newImplementation) external onlySelf {
|
|
522
|
+
_requireCustodyGoverned();
|
|
481
523
|
if (newImplementation == address(0)) revert ZeroAddress();
|
|
482
524
|
uint256 tl = _upgradeTimelock;
|
|
483
525
|
if (tl == 0) revert UpgradeTimelockNotSet();
|
|
@@ -533,6 +575,7 @@ contract AgentAccount is
|
|
|
533
575
|
/// (`onlySelf`). 0 == immediate upgrades (default,
|
|
534
576
|
/// backward-compat).
|
|
535
577
|
function setUpgradeTimelock(uint256 secs) external onlySelf {
|
|
578
|
+
_requireCustodyGoverned();
|
|
536
579
|
if (secs > MAX_UPGRADE_TIMELOCK) {
|
|
537
580
|
revert UpgradeTimelockTooLong(secs, MAX_UPGRADE_TIMELOCK);
|
|
538
581
|
}
|
|
@@ -576,6 +619,7 @@ contract AgentAccount is
|
|
|
576
619
|
* a self-call into this function.
|
|
577
620
|
*/
|
|
578
621
|
function setDelegationManager(address dm) external onlySelf {
|
|
622
|
+
_requireCustodyGoverned();
|
|
579
623
|
if (dm.code.length == 0) revert ValidatorRequired();
|
|
580
624
|
_delegationManager = dm;
|
|
581
625
|
emit DelegationManagerRotated(dm);
|
|
@@ -639,6 +683,7 @@ contract AgentAccount is
|
|
|
639
683
|
* delegation the owner has authorized.
|
|
640
684
|
*/
|
|
641
685
|
function acceptSessionDelegation(bytes32 sessionDelegationHash) external onlySelf {
|
|
686
|
+
_requireCustodyGoverned();
|
|
642
687
|
if (_acceptedSessionDelegations[sessionDelegationHash]) {
|
|
643
688
|
revert SessionDelegationAlreadyAccepted(sessionDelegationHash);
|
|
644
689
|
}
|
|
@@ -823,6 +868,10 @@ contract AgentAccount is
|
|
|
823
868
|
address module,
|
|
824
869
|
bytes calldata initData
|
|
825
870
|
) external onlySelfOrFactoryInit whenNotPaused {
|
|
871
|
+
// Wave-1/ACCT-C1: on a custody-governed account, module installs must route
|
|
872
|
+
// through the CustodyPolicy quorum. Exempt by construction at factory-init
|
|
873
|
+
// (no EXECUTOR installed yet ⇒ `_hasCustodyGovernance()` is false).
|
|
874
|
+
_requireCustodyGoverned();
|
|
826
875
|
if (module == address(0)) revert ZeroAddress();
|
|
827
876
|
if (!_isSupportedModuleType(moduleTypeId)) revert UnsupportedModuleType(moduleTypeId);
|
|
828
877
|
|
|
@@ -872,6 +921,7 @@ contract AgentAccount is
|
|
|
872
921
|
// at deploy time, and there's never a legitimate reason for
|
|
873
922
|
// the factory to do that.
|
|
874
923
|
if (msg.sender != address(this)) revert ModuleOperationNotAllowed();
|
|
924
|
+
_requireCustodyGoverned();
|
|
875
925
|
if (!_isSupportedModuleType(moduleTypeId)) revert UnsupportedModuleType(moduleTypeId);
|
|
876
926
|
|
|
877
927
|
ModulesStorage storage $ = _modulesStorage();
|
|
@@ -978,14 +1028,30 @@ contract AgentAccount is
|
|
|
978
1028
|
revert NotInstalledExecutor(msg.sender);
|
|
979
1029
|
}
|
|
980
1030
|
emit ModuleExecuted(msg.sender, target, value);
|
|
981
|
-
// CA-1: a module action
|
|
982
|
-
//
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
|
|
1031
|
+
// CA-1 + Wave-1/ACCT-C1 (spec 300): a module action from the CANONICAL CustodyPolicy is a
|
|
1032
|
+
// custody-governed context (gated upstream by its own T5 quorum + timelock). `_custodyGovCtx`
|
|
1033
|
+
// authorizes every governance mutator this call dispatches (the ONLY way past
|
|
1034
|
+
// `_requireCustodyGoverned` on a `mode>0` account); `_upgradeAuthorizedCtx` additionally exempts a
|
|
1035
|
+
// dispatched upgrade from the per-account timelock (no double delay). Both transient.
|
|
1036
|
+
//
|
|
1037
|
+
// NEW-EXEC-MODULE-1 (2026-07-05 audit, internal + external): grant that context ONLY to the
|
|
1038
|
+
// canonical CustodyPolicy the factory installs — NOT to any installed executor. Otherwise a second
|
|
1039
|
+
// (utility / automation) executor a quorum installs believing it unprivileged would silently inherit
|
|
1040
|
+
// full authority to mutate custody / DM / upgrade / modules, bypassing quorum+timelock. Matching the
|
|
1041
|
+
// specific policy address is the same check `_hasCustodyGovernance` uses. A non-canonical executor
|
|
1042
|
+
// runs WITHOUT the context, so `_requireCustodyGoverned` rejects any governance mutator it dispatches
|
|
1043
|
+
// (defense-in-depth: not reachable at v1 since the factory installs only CustodyPolicy).
|
|
1044
|
+
address f = _factory;
|
|
1045
|
+
bool custodyGoverned = f != address(0) && msg.sender == IAgentAccountFactoryView(f).custodyPolicy();
|
|
1046
|
+
if (custodyGoverned) {
|
|
1047
|
+
_custodyGovCtx = true;
|
|
1048
|
+
_upgradeAuthorizedCtx = true;
|
|
1049
|
+
}
|
|
987
1050
|
(bool ok, bytes memory ret) = target.call{value: value}(data);
|
|
988
|
-
|
|
1051
|
+
if (custodyGoverned) {
|
|
1052
|
+
_upgradeAuthorizedCtx = false;
|
|
1053
|
+
_custodyGovCtx = false;
|
|
1054
|
+
}
|
|
989
1055
|
if (!ok) {
|
|
990
1056
|
assembly { revert(add(ret, 32), mload(ret)) }
|
|
991
1057
|
}
|
|
@@ -1035,6 +1101,13 @@ contract AgentAccount is
|
|
|
1035
1101
|
function execute(address target, uint256 value, bytes calldata data) external override nonReentrant whenNotPaused {
|
|
1036
1102
|
_requireForExecute();
|
|
1037
1103
|
|
|
1104
|
+
// DM-3 (spec 300): mark a delegation-originated stack (set once at the
|
|
1105
|
+
// outermost DM frame; preserved across the legitimate DM → execute(self,
|
|
1106
|
+
// executeBatch(...)) nest) and forbid it from self-calling governance.
|
|
1107
|
+
bool setDelCtx = (msg.sender == _delegationManager && !_delegationExecCtx);
|
|
1108
|
+
if (setDelCtx) _delegationExecCtx = true;
|
|
1109
|
+
_rejectDelegatedGovernance(target, data);
|
|
1110
|
+
|
|
1038
1111
|
ModulesStorage storage $ = _modulesStorage();
|
|
1039
1112
|
address[] memory hooks = $.installedList[MODULE_TYPE_HOOK];
|
|
1040
1113
|
bytes[] memory hookData = new bytes[](hooks.length);
|
|
@@ -1062,6 +1135,8 @@ contract AgentAccount is
|
|
|
1062
1135
|
for (uint256 i = 0; i < hooks.length; i++) {
|
|
1063
1136
|
IERC7579HookLike(hooks[i]).postCheck(hookData[i]);
|
|
1064
1137
|
}
|
|
1138
|
+
|
|
1139
|
+
if (setDelCtx) _delegationExecCtx = false;
|
|
1065
1140
|
}
|
|
1066
1141
|
|
|
1067
1142
|
/// @notice Atomically execute multiple calls from this account.
|
|
@@ -1088,6 +1163,13 @@ contract AgentAccount is
|
|
|
1088
1163
|
function executeBatch(Call[] calldata calls) external override whenNotPaused {
|
|
1089
1164
|
_requireForExecute();
|
|
1090
1165
|
|
|
1166
|
+
// DM-3 (spec 300): same delegation-origin marking as `execute`. Preserved
|
|
1167
|
+
// across the DM → execute(self, executeBatch(...)) nest so a governance
|
|
1168
|
+
// self-call in ANY inner call is caught even though this frame's
|
|
1169
|
+
// msg.sender is address(this).
|
|
1170
|
+
bool setDelCtx = (msg.sender == _delegationManager && !_delegationExecCtx);
|
|
1171
|
+
if (setDelCtx) _delegationExecCtx = true;
|
|
1172
|
+
|
|
1091
1173
|
ModulesStorage storage $ = _modulesStorage();
|
|
1092
1174
|
address[] memory hooks = $.installedList[MODULE_TYPE_HOOK];
|
|
1093
1175
|
bytes[] memory hookData = new bytes[](hooks.length);
|
|
@@ -1101,6 +1183,7 @@ contract AgentAccount is
|
|
|
1101
1183
|
}
|
|
1102
1184
|
|
|
1103
1185
|
for (uint256 i = 0; i < calls.length; i++) {
|
|
1186
|
+
_rejectDelegatedGovernance(calls[i].target, calls[i].data);
|
|
1104
1187
|
(bool ok, bytes memory ret) = calls[i].target.call{ value: calls[i].value }(calls[i].data);
|
|
1105
1188
|
if (!ok) {
|
|
1106
1189
|
assembly {
|
|
@@ -1113,6 +1196,8 @@ contract AgentAccount is
|
|
|
1113
1196
|
for (uint256 i = 0; i < hooks.length; i++) {
|
|
1114
1197
|
IERC7579HookLike(hooks[i]).postCheck(hookData[i]);
|
|
1115
1198
|
}
|
|
1199
|
+
|
|
1200
|
+
if (setDelCtx) _delegationExecCtx = false;
|
|
1116
1201
|
}
|
|
1117
1202
|
|
|
1118
1203
|
// ─── ERC-4337 ───────────────────────────────────────────────────
|
|
@@ -1146,6 +1231,68 @@ contract AgentAccount is
|
|
|
1146
1231
|
}
|
|
1147
1232
|
}
|
|
1148
1233
|
|
|
1234
|
+
// ─── Wave 1 — account authorization boundary (spec 300) ─────────
|
|
1235
|
+
|
|
1236
|
+
/// @dev True when this account is custody-governed — i.e. the canonical
|
|
1237
|
+
/// CustodyPolicy the factory installs for `mode > 0` accounts is installed
|
|
1238
|
+
/// as an EXECUTOR here. Governance mutators then require the
|
|
1239
|
+
/// `executeFromModule` (quorum+timelock) path. A `mode 0` person account
|
|
1240
|
+
/// (no CustodyPolicy) self-governs; installing an UNRELATED utility executor
|
|
1241
|
+
/// does NOT make it custody-governed (we check the specific policy address,
|
|
1242
|
+
/// not "any executor"). If the factory is unknown (legacy direct deploy),
|
|
1243
|
+
/// fall back to person semantics.
|
|
1244
|
+
function _hasCustodyGovernance() internal view returns (bool) {
|
|
1245
|
+
address f = _factory;
|
|
1246
|
+
if (f == address(0)) return false;
|
|
1247
|
+
address cp = IAgentAccountFactoryView(f).custodyPolicy();
|
|
1248
|
+
if (cp == address(0)) return false;
|
|
1249
|
+
return _modulesStorage().installed[MODULE_TYPE_EXECUTOR][cp];
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/// @dev ACCT-C1 gate: on a custody-governed account, a governance mutator is
|
|
1253
|
+
/// reachable only through `executeFromModule` (which sets `_custodyGovCtx`).
|
|
1254
|
+
/// A single-signer UserOp `execute(self, adminSelector)` self-call leaves the
|
|
1255
|
+
/// flag unset and reverts here, so the custody quorum can no longer be bypassed.
|
|
1256
|
+
function _requireCustodyGoverned() internal view {
|
|
1257
|
+
if (_hasCustodyGovernance() && !_custodyGovCtx) revert AdminRequiresCustodyQuorum();
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/// @dev DM-3 gate: a delegation-originated stack may never self-call an account
|
|
1261
|
+
/// governance selector. `_delegationExecCtx` is set at the outermost
|
|
1262
|
+
/// DM-driven `execute`/`executeBatch` and persists across the legitimate
|
|
1263
|
+
/// `DM → execute(self, executeBatch(...))` nest, so a nested governance
|
|
1264
|
+
/// self-call is caught regardless of mode.
|
|
1265
|
+
function _rejectDelegatedGovernance(address target, bytes calldata data) internal view {
|
|
1266
|
+
if (
|
|
1267
|
+
_delegationExecCtx &&
|
|
1268
|
+
target == address(this) &&
|
|
1269
|
+
data.length >= 4 &&
|
|
1270
|
+
_isGovernanceSelector(bytes4(data[0:4]))
|
|
1271
|
+
) {
|
|
1272
|
+
revert DelegatedGovernanceForbidden();
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/// @dev The account-governance selector set — mutators that change custody,
|
|
1277
|
+
/// the DelegationManager, installed modules, or the upgrade path. Kept in
|
|
1278
|
+
/// sync with the `_requireCustodyGoverned`-gated functions above.
|
|
1279
|
+
function _isGovernanceSelector(bytes4 s) internal pure returns (bool) {
|
|
1280
|
+
return
|
|
1281
|
+
s == this.setDelegationManager.selector ||
|
|
1282
|
+
s == UUPSUpgradeable.upgradeToAndCall.selector ||
|
|
1283
|
+
s == this.setUpgradeTimelock.selector ||
|
|
1284
|
+
s == this.scheduleUpgrade.selector ||
|
|
1285
|
+
s == this.executePendingUpgrade.selector ||
|
|
1286
|
+
s == this.cancelPendingUpgrade.selector ||
|
|
1287
|
+
s == this.installModule.selector ||
|
|
1288
|
+
s == this.uninstallModule.selector ||
|
|
1289
|
+
s == IAgentAccount.addCustodian.selector ||
|
|
1290
|
+
s == IAgentAccount.removeCustodian.selector ||
|
|
1291
|
+
s == this.addPasskey.selector ||
|
|
1292
|
+
s == this.removePasskey.selector ||
|
|
1293
|
+
s == this.acceptSessionDelegation.selector;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1149
1296
|
// ─── ERC-1271 ───────────────────────────────────────────────────
|
|
1150
1297
|
|
|
1151
1298
|
/// @dev 32-byte ERC-6492 magic suffix — `0x6492…6492` repeated.
|
|
@@ -1330,6 +1477,19 @@ contract AgentAccount is
|
|
|
1330
1477
|
return _passkeyStorage().piaToCredentialId[account] != bytes32(0);
|
|
1331
1478
|
}
|
|
1332
1479
|
|
|
1480
|
+
/// @notice EXT-ISCUSTODIAN-1 (2026-07-05 audit): like `isCustodian` but EXCLUDES the account itself.
|
|
1481
|
+
/// @dev `isCustodian(address(this)) == true` is deliberate (account-actor compatibility), but that
|
|
1482
|
+
/// footgun can recreate the ACCT-C2 quorum-collapse class in a FUTURE consumer that reuses
|
|
1483
|
+
/// `isCustodian` as if it meant "external signer." Any quorum / authority consumer that must count
|
|
1484
|
+
/// only real external custodians / passkeys (never the account) should use THIS view. ACCT-C2 fixed
|
|
1485
|
+
/// the current CustodyPolicy quorum at the counting site (reject signer==account); this gives new
|
|
1486
|
+
/// consumers the safe primitive so the class can't reappear off-path.
|
|
1487
|
+
function isExternalCustodianOrPasskey(address account) external view returns (bool) {
|
|
1488
|
+
if (account == address(this)) return false;
|
|
1489
|
+
if (_externalCustodians[account]) return true;
|
|
1490
|
+
return _passkeyStorage().piaToCredentialId[account] != bytes32(0);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1333
1493
|
/// @inheritdoc IAgentAccount
|
|
1334
1494
|
/// @dev Counts EOA/contract custodians PLUS registered passkeys —
|
|
1335
1495
|
/// each passkey contributes one PIA-custodian. Matches the
|
|
@@ -1359,6 +1519,7 @@ contract AgentAccount is
|
|
|
1359
1519
|
/// the ERC-165 marker. Passkey custodians are not added here;
|
|
1360
1520
|
/// use `addPasskey` instead.
|
|
1361
1521
|
function addCustodian(address owner) external override onlySelf whenNotPaused {
|
|
1522
|
+
_requireCustodyGoverned();
|
|
1362
1523
|
if (owner == address(0)) revert ZeroAddress();
|
|
1363
1524
|
if (_externalCustodians[owner]) revert CustodianAlreadyExists(owner);
|
|
1364
1525
|
if (_isAgenticPrimitivesAgent(owner)) {
|
|
@@ -1402,6 +1563,7 @@ contract AgentAccount is
|
|
|
1402
1563
|
/// owner if there are also no registered passkeys. A passkey-only
|
|
1403
1564
|
/// account is allowed, but a zero-signer account is not.
|
|
1404
1565
|
function removeCustodian(address owner) external override onlySelf {
|
|
1566
|
+
_requireCustodyGoverned();
|
|
1405
1567
|
if (!_externalCustodians[owner]) revert CustodianDoesNotExist(owner);
|
|
1406
1568
|
if (_externalCustodianCount == 1 && _passkeyStorage().count == 0) revert CannotRemoveLastCustodian();
|
|
1407
1569
|
_externalCustodians[owner] = false;
|
|
@@ -1466,6 +1628,7 @@ contract AgentAccount is
|
|
|
1466
1628
|
uint256 y,
|
|
1467
1629
|
bytes32 rpIdHash
|
|
1468
1630
|
) external onlySelf {
|
|
1631
|
+
_requireCustodyGoverned();
|
|
1469
1632
|
if (x == 0 || y == 0) revert InvalidPasskeyPublicKey();
|
|
1470
1633
|
// Audit C-6: zero digest poisons piaToCredentialId mapping.
|
|
1471
1634
|
if (credentialIdDigest == bytes32(0)) revert InvalidCredentialIdDigest();
|
|
@@ -1490,6 +1653,7 @@ contract AgentAccount is
|
|
|
1490
1653
|
/// AND passkeys together. Also clears the credential's PIA
|
|
1491
1654
|
/// entry so `isCustodian(pia)` flips back to false.
|
|
1492
1655
|
function removePasskey(bytes32 credentialIdDigest) external onlySelf {
|
|
1656
|
+
_requireCustodyGoverned();
|
|
1493
1657
|
PasskeyStorage storage $ = _passkeyStorage();
|
|
1494
1658
|
if (!$.registered[credentialIdDigest]) revert PasskeyNotRegistered(credentialIdDigest);
|
|
1495
1659
|
if (_externalCustodianCount + $.count == 1) revert CannotRemoveLastSigner();
|
|
@@ -77,6 +77,9 @@ contract AgentAccountFactory is GovernanceManaged {
|
|
|
77
77
|
error NoInitialSigner();
|
|
78
78
|
error InsufficientTrusteesForMode(uint8 mode, uint256 actual, uint256 required);
|
|
79
79
|
error TrusteesRequiredForRecoverableMode(uint8 mode);
|
|
80
|
+
/// @notice ACCT-M1 (spec 300): a mode-3 (org) account needs ≥2 signers so its separation-of-duties
|
|
81
|
+
/// (disjoint propose + apply quorums) is satisfiable — a 1-signer org can never split duties.
|
|
82
|
+
error InfeasibleOrgConfig(uint8 nSigners);
|
|
80
83
|
error ZeroAddress();
|
|
81
84
|
/// @notice R9.6 / ATL-SEC-05 -- thrown when `params.custodians.length`
|
|
82
85
|
/// exceeds `MAX_INITIAL_CUSTODIANS`. Without this cap,
|
|
@@ -308,6 +311,19 @@ contract AgentAccountFactory is GovernanceManaged {
|
|
|
308
311
|
thresholds[t] = CustodyPolicy(custodyPolicy).defaultApprovals(uint8(nSigners), t);
|
|
309
312
|
}
|
|
310
313
|
|
|
314
|
+
// ACCT-M1 (spec 300): mode-3 (org) separation-of-duties requires DISJOINT propose + apply quorums
|
|
315
|
+
// for the T4/T5 tiers, so a satisfiable config needs 2×threshold DISTINCT signers. The default
|
|
316
|
+
// matrix sets T4/T5 = N for small N (all custodians), which would demand 2N signers — making every
|
|
317
|
+
// such change permanently un-executable (the account silently locks). Cap the SoD-constrained
|
|
318
|
+
// thresholds at floor(N/2) so a disjoint quorum always exists, and require ≥2 signers (a 1-signer
|
|
319
|
+
// org can't split duties). Modes 0-2 have no SoD, so their thresholds are unchanged.
|
|
320
|
+
if (params.mode == 3) {
|
|
321
|
+
if (nSigners < 2) revert InfeasibleOrgConfig(uint8(nSigners));
|
|
322
|
+
uint8 sodCap = uint8(nSigners / 2); // ≥ 1 since nSigners ≥ 2
|
|
323
|
+
if (thresholds[4] > sodCap) thresholds[4] = sodCap;
|
|
324
|
+
if (thresholds[5] > sodCap) thresholds[5] = sodCap;
|
|
325
|
+
}
|
|
326
|
+
|
|
311
327
|
uint32[7] memory timelocks;
|
|
312
328
|
timelocks[4] = timelockOverrides[4] == 0 ? uint32(1 hours) : timelockOverrides[4];
|
|
313
329
|
timelocks[5] = timelockOverrides[5] == 0 ? uint32(24 hours) : timelockOverrides[5];
|
|
@@ -260,16 +260,21 @@ contract SmartAgentPaymaster is BasePaymaster {
|
|
|
260
260
|
return _pausedMirror;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
/// @dev Read the canonical governance pause flag. Used ONLY by the
|
|
264
|
-
///
|
|
265
|
-
///
|
|
266
|
-
///
|
|
263
|
+
/// @dev Read the canonical governance pause flag. Used ONLY by the out-of-band sync path, never
|
|
264
|
+
/// during validation (ERC-7562 scope). L1 (seam audit): distinguish "no governance configured"
|
|
265
|
+
/// (legacy / test deploy — not paused, by design) from "governance IS configured but the read
|
|
266
|
+
/// faulted / returned malformed data" (a governance outage or non-conforming contract), which now
|
|
267
|
+
/// FAILS CLOSED — treat as paused so a sync during a governance fault halts sponsorship rather
|
|
268
|
+
/// than fail-open sponsoring. Only a clean, decodable `false` permits sponsorship.
|
|
267
269
|
function _governanceIsPaused() internal view returns (bool) {
|
|
268
|
-
|
|
270
|
+
// Unconfigured governance (unset, or an EOA with no code) → not paused (legacy/test path).
|
|
271
|
+
if (governance == address(0) || governance.code.length == 0) return false;
|
|
269
272
|
(bool ok, bytes memory data) = governance.staticcall(
|
|
270
273
|
abi.encodeWithSelector(IGovernanceView.isPaused.selector)
|
|
271
274
|
);
|
|
272
|
-
|
|
275
|
+
// Configured but faulted (call failed or undecodable) → fail closed (paused).
|
|
276
|
+
if (!ok || data.length < 32) return true;
|
|
277
|
+
return abi.decode(data, (bool));
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
// ─── PM-2: governance-timelocked deposit withdrawal ────────────────
|
|
@@ -109,6 +109,10 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
|
|
|
109
109
|
// Phase A.5 errors.
|
|
110
110
|
error NotDelegatorOrDelegate();
|
|
111
111
|
error HashMismatch();
|
|
112
|
+
/// @dev EXT-NOCODE-HOOK-1: a caveat enforcer with no code — a call to it would
|
|
113
|
+
/// succeed vacuously (fail-OPEN), so the live before/after hook loops reject it,
|
|
114
|
+
/// matching the view path (`verifyAuthorizationForCall` → "no-code-enforcer").
|
|
115
|
+
error NoCodeEnforcer(address enforcer);
|
|
112
116
|
/// @dev Thrown by the deprecated permissionless `revokeDelegation(bytes32)`
|
|
113
117
|
/// path. Migrate callers to `revokeDelegationByOwner(Delegation)`.
|
|
114
118
|
error LegacyRevocationDisabled();
|
|
@@ -454,6 +458,9 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
|
|
|
454
458
|
) internal {
|
|
455
459
|
bytes32 dHash = hashDelegation(d);
|
|
456
460
|
for (uint256 j = 0; j < d.caveats.length; j++) {
|
|
461
|
+
// EXT-NOCODE-HOOK-1: fail closed on a no-code enforcer (a call would succeed vacuously),
|
|
462
|
+
// symmetric with the view path's "no-code-enforcer" reject — never rely on call semantics.
|
|
463
|
+
if (d.caveats[j].enforcer.code.length == 0) revert NoCodeEnforcer(d.caveats[j].enforcer);
|
|
457
464
|
ICaveatEnforcer(d.caveats[j].enforcer).beforeHook(
|
|
458
465
|
d.caveats[j].terms,
|
|
459
466
|
d.caveats[j].args,
|
|
@@ -475,6 +482,8 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
|
|
|
475
482
|
) internal {
|
|
476
483
|
bytes32 dHash = hashDelegation(d);
|
|
477
484
|
for (uint256 j = 0; j < d.caveats.length; j++) {
|
|
485
|
+
// EXT-NOCODE-HOOK-1: fail closed on a no-code enforcer, symmetric with the before-hook + view path.
|
|
486
|
+
if (d.caveats[j].enforcer.code.length == 0) revert NoCodeEnforcer(d.caveats[j].enforcer);
|
|
478
487
|
ICaveatEnforcer(d.caveats[j].enforcer).afterHook(
|
|
479
488
|
d.caveats[j].terms,
|
|
480
489
|
d.caveats[j].args,
|
|
@@ -309,10 +309,19 @@ contract CustodyPolicy {
|
|
|
309
309
|
error AdminInsufficientQuorum(uint256 supplied, uint8 required);
|
|
310
310
|
error AdminDuplicateOrUnsortedSigner(address signer);
|
|
311
311
|
error AdminUnauthorizedSigner(address signer);
|
|
312
|
+
/// @notice ACCT-C2 (spec 300) — the account itself was presented as a quorum
|
|
313
|
+
/// signer (via `isCustodian(address(this))==true` or the `v=0`
|
|
314
|
+
/// self-referential ERC-1271 slot). A self-signature must never count
|
|
315
|
+
/// toward the quorum, else one custodian forges an extra member.
|
|
316
|
+
error AdminSelfSignerNotAllowed(address account);
|
|
312
317
|
error TrusteeAlreadyExists(address guardian);
|
|
313
318
|
error TrusteeDoesNotExist(address guardian);
|
|
314
319
|
error InvalidMode(uint8 mode_);
|
|
315
320
|
error InvalidThresholdValue(uint8 thr);
|
|
321
|
+
/// @dev EXT-CHANGEMODE-1: a runtime ChangeMode into org mode (3) with an infeasible signer
|
|
322
|
+
/// count (< 2, so separation-of-duties can't split proposers from appliers). Mirrors the
|
|
323
|
+
/// factory's install-time InfeasibleOrgConfig.
|
|
324
|
+
error InfeasibleOrgConfig(uint8 nSigners);
|
|
316
325
|
/// @dev CP-1: a tier that must be configured (T4/T5 at install, T6 at read)
|
|
317
326
|
/// was left unset — refuse rather than fall to the 1-of-n read fallback.
|
|
318
327
|
error UnconfiguredTier(uint8 tier);
|
|
@@ -748,6 +757,11 @@ contract CustodyPolicy {
|
|
|
748
757
|
);
|
|
749
758
|
if (signer <= prev) revert AdminDuplicateOrUnsortedSigner(signer);
|
|
750
759
|
prev = signer;
|
|
760
|
+
// ACCT-C2 (spec 300): the account is never its own quorum signer. Both
|
|
761
|
+
// exploit shapes — `isCustodian(address(this))==true` and the `v=0`
|
|
762
|
+
// self-referential ERC-1271 slot that recovers `signer==account` — land
|
|
763
|
+
// here as `signer == account`, so one guard closes both.
|
|
764
|
+
if (signer == account) revert AdminSelfSignerNotAllowed(account);
|
|
751
765
|
if (vargs.guardianMode) {
|
|
752
766
|
if (!c.trustees[signer]) revert UnauthorizedTrustee(signer);
|
|
753
767
|
} else {
|
|
@@ -874,6 +888,23 @@ contract CustodyPolicy {
|
|
|
874
888
|
if (newMode > 3) revert InvalidMode(newMode);
|
|
875
889
|
uint8 oldMode = c.mode;
|
|
876
890
|
if (newMode == 0 && c.trusteeCount > 0) revert CannotDowngradeWithTrustees();
|
|
891
|
+
// EXT-CHANGEMODE-1 (2026-07-05 audit): a runtime ChangeMode must re-run the factory's install-time
|
|
892
|
+
// mode-3 feasibility so the account can't enter an un-executable / liveness-hostile org mode.
|
|
893
|
+
// Org SoD needs ≥2 signers (distinct proposers + appliers) and T4/T5 thresholds within the
|
|
894
|
+
// floor(N/2) cap (else 2×threshold > N → no disjoint quorum → the tier silently locks). Require the
|
|
895
|
+
// signer floor; cap the SoD-constrained thresholds (mirrors the factory, avoids a locked mode).
|
|
896
|
+
if (newMode == 3) {
|
|
897
|
+
uint256 n = IAgentAccount(account).custodianCount();
|
|
898
|
+
if (n < 2) revert InfeasibleOrgConfig(uint8(n));
|
|
899
|
+
uint8 sodCap = uint8(n / 2); // ≥ 1 since n ≥ 2
|
|
900
|
+
for (uint8 tier = 4; tier <= 5; tier++) {
|
|
901
|
+
uint8 v = c.approvalsRequiredByTier[tier];
|
|
902
|
+
if (v > sodCap) {
|
|
903
|
+
c.approvalsRequiredByTier[tier] = sodCap;
|
|
904
|
+
emit ThresholdChanged(account, tier, v, sodCap);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
877
908
|
c.mode = newMode;
|
|
878
909
|
emit ModeChanged(account, oldMode, newMode);
|
|
879
910
|
}
|
|
@@ -958,6 +989,13 @@ contract CustodyPolicy {
|
|
|
958
989
|
if (newCount == 0) revert InvalidThresholdValue(newCount);
|
|
959
990
|
uint256 n = IAgentAccount(account).custodianCount();
|
|
960
991
|
if (uint256(newCount) > n) revert InvalidThresholdValue(newCount);
|
|
992
|
+
// acct-mode3-sod-cap-runtime (2026-07-05 audit): the factory caps mode-3 (org) T4/T5 thresholds at
|
|
993
|
+
// floor(N/2) at install so a DISJOINT propose+apply quorum always exists (2×threshold ≤ N). A later
|
|
994
|
+
// ChangeApprovalsRequired must not raise them past that cap and silently lock the tier — reject an
|
|
995
|
+
// explicit over-cap request loud (the org can lower N-constraints or use a lower threshold).
|
|
996
|
+
if (c.mode == 3 && (tier == 4 || tier == 5) && uint256(newCount) > n / 2) {
|
|
997
|
+
revert InvalidThresholdValue(newCount);
|
|
998
|
+
}
|
|
961
999
|
uint8 oldValue = c.approvalsRequiredByTier[tier];
|
|
962
1000
|
if (oldValue == 0) oldValue = 1; // mirror _approvalsValue fallback
|
|
963
1001
|
c.approvalsRequiredByTier[tier] = newCount;
|
|
@@ -58,6 +58,13 @@ contract PaymentEnforcer is ICaveatEnforcer {
|
|
|
58
58
|
/// delegator => delegationHash => nonce => consumed (on-chain replay guard)
|
|
59
59
|
mapping(address => mapping(bytes32 => mapping(bytes32 => bool))) private _nonceUsed;
|
|
60
60
|
|
|
61
|
+
/// @notice M1 (seam audit): keccak256(delegator, delegationHash, nonce) => (treasury balanceBefore + 1).
|
|
62
|
+
/// Snapshotted in `beforeHook`; consumed in `afterHook` to verify the transfer ACTUALLY moved
|
|
63
|
+
/// `amount` to the treasury before the settlement receipt is written. `+1` sentinel so a set
|
|
64
|
+
/// entry is distinguishable from an unset slot even when the treasury balance is 0. Set and
|
|
65
|
+
/// deleted within a single non-reentrant `redeemDelegation`, so it never persists across txs.
|
|
66
|
+
mapping(bytes32 => uint256) private _settleSnap;
|
|
67
|
+
|
|
61
68
|
/// @notice The ONLY authorized caller of the hooks (EXT-PMT-1 / C-NEW-1). Unlike the stateless
|
|
62
69
|
/// enforcers (timestamp/value/targets/methods), this enforcer MUTATES state (budget, nonce)
|
|
63
70
|
/// and writes a settlement receipt — so it is NOT safe to call externally. A direct caller
|
|
@@ -93,6 +100,10 @@ contract PaymentEnforcer is ICaveatEnforcer {
|
|
|
93
100
|
error FrequencyExceeded(uint32 callsInWindow, uint32 maxRedemptionsPerWindow);
|
|
94
101
|
error NonceReused(bytes32 nonce);
|
|
95
102
|
error NotDelegationManager(address caller);
|
|
103
|
+
/// @notice M1 (seam audit): the transfer did not move `amount` to the treasury (a token that returns
|
|
104
|
+
/// false without reverting, a fee-on-transfer skim, or a missing beforeHook snapshot) — so no
|
|
105
|
+
/// settlement receipt is written and the whole redemption reverts.
|
|
106
|
+
error SettlementNotObserved();
|
|
96
107
|
|
|
97
108
|
/// @dev The hooks are state-mutating + receipt-emitting, so ONLY the DelegationManager may invoke
|
|
98
109
|
/// them (EXT-PMT-1). `_runBeforeHooks`/`_runAfterHooks` call the enforcer directly, so during a
|
|
@@ -173,19 +184,49 @@ contract PaymentEnforcer is ICaveatEnforcer {
|
|
|
173
184
|
delegator, delegationHash, mandateId, treasury, asset, amount, nonce, resourceHash, nextSpent, nextCalls
|
|
174
185
|
);
|
|
175
186
|
|
|
176
|
-
//
|
|
177
|
-
//
|
|
187
|
+
// M1 (seam audit): snapshot the treasury balance BEFORE the transfer. The trustless settlement
|
|
188
|
+
// receipt (PAY-CON-4) is written in `afterHook` ONLY once the transfer is observed to have moved
|
|
189
|
+
// `amount` to the treasury — a token that returns false without reverting (or a fee-on-transfer
|
|
190
|
+
// skim) then records NO receipt and reverts the whole redemption, so "a settled charge always has
|
|
191
|
+
// a receipt and an unsettled one never does" holds against the value that ACTUALLY moved.
|
|
192
|
+
_settleSnap[_settleKey(delegator, delegationHash, nonce)] = IERC20(asset).balanceOf(treasury) + 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function afterHook(
|
|
196
|
+
bytes calldata terms,
|
|
197
|
+
bytes calldata args,
|
|
198
|
+
bytes32 delegationHash,
|
|
199
|
+
address delegator,
|
|
200
|
+
address, /*redeemer*/
|
|
201
|
+
address, /*target*/
|
|
202
|
+
uint256, /*value*/
|
|
203
|
+
bytes calldata callData
|
|
204
|
+
) external override onlyDelegationManager {
|
|
205
|
+
// Only this enforcer's own 192-byte-terms caveat carries a snapshot (beforeHook validated the
|
|
206
|
+
// shape); ignore anything else defensively so a malformed sibling caveat can't revert settlement.
|
|
207
|
+
if (terms.length != 192) return;
|
|
208
|
+
(address treasury, address asset,,,,) = abi.decode(terms, (address, address, uint256, uint256, uint32, uint32));
|
|
209
|
+
(bytes32 mandateId, bytes32 nonce, bytes32 resourceHash) = abi.decode(args, (bytes32, bytes32, bytes32));
|
|
210
|
+
(, uint256 amount) = abi.decode(callData[4:], (address, uint256));
|
|
211
|
+
|
|
212
|
+
bytes32 key = _settleKey(delegator, delegationHash, nonce);
|
|
213
|
+
uint256 snap = _settleSnap[key];
|
|
214
|
+
if (snap == 0) revert SettlementNotObserved(); // no beforeHook snapshot (defensive)
|
|
215
|
+
delete _settleSnap[key];
|
|
216
|
+
|
|
217
|
+
// The treasury must have received AT LEAST `amount` since the snapshot. A silent transfer failure
|
|
218
|
+
// (balance unchanged) or a fee-on-transfer short-delivery fails this and reverts the redemption.
|
|
219
|
+
if (IERC20(asset).balanceOf(treasury) < (snap - 1) + amount) revert SettlementNotObserved();
|
|
220
|
+
|
|
178
221
|
if (address(receiptRegistry) != address(0)) {
|
|
179
222
|
receiptRegistry.record(mandateId, delegator, treasury, asset, amount, resourceHash, delegationHash, nonce);
|
|
180
223
|
}
|
|
181
224
|
}
|
|
182
225
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
onlyDelegationManager
|
|
188
|
-
{}
|
|
226
|
+
/// @dev Unique per redemption — the nonce is single-use per [delegator][delegationHash].
|
|
227
|
+
function _settleKey(address delegator, bytes32 delegationHash, bytes32 nonce) private pure returns (bytes32) {
|
|
228
|
+
return keccak256(abi.encode(delegator, delegationHash, nonce));
|
|
229
|
+
}
|
|
189
230
|
|
|
190
231
|
// ── views (off-chain budget display + idempotency checks) ──
|
|
191
232
|
|
|
@@ -28,6 +28,9 @@ library AgentProfilePredicates {
|
|
|
28
28
|
// ─── Identity-only ──────────────────────────────────────────────
|
|
29
29
|
|
|
30
30
|
bytes32 internal constant ATL_DESCRIPTION = keccak256("atl:description");
|
|
31
|
+
/// spec 282 — publicly-asserted skill labels (string; the indexer projects to approf:skills, the
|
|
32
|
+
/// discovery matcher ranks on it). The agent's full private skill claims live in agent-skills VCs.
|
|
33
|
+
bytes32 internal constant ATL_SKILLS = keccak256("atl:skills");
|
|
31
34
|
bytes32 internal constant ATL_HOMEPAGE = keccak256("atl:homepage");
|
|
32
35
|
bytes32 internal constant ATL_AVATAR = keccak256("atl:avatar");
|
|
33
36
|
bytes32 internal constant ATL_PROFILE_SCHEMA_URI = keccak256("atl:profileSchemaURI");
|
|
@@ -55,6 +55,17 @@ library AgentNamePredicates {
|
|
|
55
55
|
/// (per ADR-0008). `string`.
|
|
56
56
|
bytes32 internal constant ATL_NATIVE_ID = keccak256("atl:nativeId");
|
|
57
57
|
|
|
58
|
+
/// `atl:connectionKind` — connection-bootstrap credential KIND (spec 280).
|
|
59
|
+
/// Stored as `bytes32` (hashed enum). Bound to CONNECTION_KIND_ENUM. OPT-IN,
|
|
60
|
+
/// owner-set, PUBLIC. Advertises how a returning person connects by name.
|
|
61
|
+
bytes32 internal constant ATL_CONNECTION_KIND = keccak256("atl:connectionKind");
|
|
62
|
+
|
|
63
|
+
/// `atl:connectionAddress` — connection-bootstrap credential ADDRESS (spec 280).
|
|
64
|
+
/// The custodian EOA (wallet) or social KMS C_sub. `address`. OPT-IN, owner-set,
|
|
65
|
+
/// PUBLIC pre-select hint only (NOT required to connect). Deliberate, documented
|
|
66
|
+
/// name→credential index (ADR-0040 amendment). Never set automatically.
|
|
67
|
+
bytes32 internal constant ATL_CONNECTION_ADDRESS = keccak256("atl:connectionAddress");
|
|
68
|
+
|
|
58
69
|
// ─── Shape + enum-set ids ───────────────────────────────────────
|
|
59
70
|
|
|
60
71
|
/// `atl:AgentName` — the class id for ShapeRegistry validation.
|
|
@@ -71,4 +82,14 @@ library AgentNamePredicates {
|
|
|
71
82
|
bytes32 internal constant AGENT_KIND_PERSON = keccak256("person");
|
|
72
83
|
bytes32 internal constant AGENT_KIND_ORG = keccak256("org");
|
|
73
84
|
bytes32 internal constant AGENT_KIND_SERVICE = keccak256("service");
|
|
85
|
+
|
|
86
|
+
/// Enum set bound to `atl:connectionKind` (spec 280). Contains the
|
|
87
|
+
/// five hashed member ids below.
|
|
88
|
+
bytes32 internal constant CONNECTION_KIND_ENUM = keccak256("atl:ConnectionKindEnum");
|
|
89
|
+
|
|
90
|
+
bytes32 internal constant CONNECTION_KIND_WALLET = keccak256("wallet");
|
|
91
|
+
bytes32 internal constant CONNECTION_KIND_GOOGLE = keccak256("google");
|
|
92
|
+
bytes32 internal constant CONNECTION_KIND_YOUVERSION = keccak256("youversion");
|
|
93
|
+
bytes32 internal constant CONNECTION_KIND_PASSKEY = keccak256("passkey");
|
|
94
|
+
bytes32 internal constant CONNECTION_KIND_MULTI = keccak256("multi");
|
|
74
95
|
}
|
|
@@ -261,7 +261,10 @@ contract AgentNameRegistry is GovernanceManaged {
|
|
|
261
261
|
|
|
262
262
|
bytes32 lh = keccak256(bytes(label));
|
|
263
263
|
childNode = keccak256(abi.encodePacked(parentNode, lh));
|
|
264
|
-
|
|
264
|
+
// M2 (seam audit): an EXPIRED name is reclaimable (parent-authorized re-register). Only a LIVE
|
|
265
|
+
// record blocks re-registration — previously expiry was inert and a name could never be reclaimed.
|
|
266
|
+
bool reclaiming = _records[childNode].registeredAt != 0;
|
|
267
|
+
if (reclaiming && !isExpired(childNode)) revert NodeAlreadyExists();
|
|
265
268
|
|
|
266
269
|
_records[childNode] = NameRecord({
|
|
267
270
|
owner: newOwner,
|
|
@@ -274,7 +277,8 @@ contract AgentNameRegistry is GovernanceManaged {
|
|
|
274
277
|
});
|
|
275
278
|
|
|
276
279
|
_children[parentNode][lh] = childNode;
|
|
277
|
-
_childLabels
|
|
280
|
+
// Only index a brand-new child; a reclaim reuses the existing _childLabels entry (no duplicate).
|
|
281
|
+
if (!reclaiming) _childLabels[parentNode].push(lh);
|
|
278
282
|
_label[childNode] = label;
|
|
279
283
|
|
|
280
284
|
emit NameRegistered(childNode, parentNode, label, newOwner, resolverContract, expiry);
|
|
@@ -352,7 +356,13 @@ contract AgentNameRegistry is GovernanceManaged {
|
|
|
352
356
|
function parent(bytes32 node) external view returns (bytes32) { return _records[node].parent; }
|
|
353
357
|
function labelhash(bytes32 node) external view returns (bytes32) { return _records[node].labelhash; }
|
|
354
358
|
function expiry(bytes32 node) external view returns (uint64) { return _records[node].expiry; }
|
|
355
|
-
|
|
359
|
+
/// @dev M2 (seam audit): an EXPIRED record does NOT exist for read purposes — so the resolver
|
|
360
|
+
/// (resolveName/resolveString, which gate on recordExists) and any other reader stop resolving
|
|
361
|
+
/// an expired name. Expiry now provides real revocation, not a decorative field. Registry
|
|
362
|
+
/// mutators gate on the raw `registeredAt`/`isExpired`, not this, so re-registration is unaffected.
|
|
363
|
+
function recordExists(bytes32 node) external view returns (bool) {
|
|
364
|
+
return _records[node].registeredAt != 0 && !isExpired(node);
|
|
365
|
+
}
|
|
356
366
|
function registeredAt(bytes32 node) external view returns (uint64) { return _records[node].registeredAt; }
|
|
357
367
|
|
|
358
368
|
function childNode(bytes32 parentNode, bytes32 lh) external view returns (bytes32) {
|