@oracle-agent/oracle 0.24.3 → 0.24.5
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/README.md +3 -0
- package/dist/assets/protocols/templates/safe-erc20/README.md +9 -0
- package/dist/assets/protocols/templates/safe-erc20/SECURITY.md +16 -0
- package/dist/assets/protocols/templates/safe-erc20/foundry.toml +12 -0
- package/dist/assets/protocols/templates/safe-erc20/remappings.txt +2 -0
- package/dist/assets/protocols/templates/safe-erc20/src/SafeERC20.sol +53 -0
- package/dist/assets/protocols/templates/safe-erc20/test/SafeERC20.t.sol +72 -0
- package/dist/assets/protocols/templates/safe-erc721-drop/README.md +3 -0
- package/dist/assets/protocols/templates/safe-erc721-drop/SECURITY.md +3 -0
- package/dist/assets/protocols/templates/safe-erc721-drop/foundry.toml +11 -0
- package/dist/assets/protocols/templates/safe-erc721-drop/remappings.txt +2 -0
- package/dist/assets/protocols/templates/safe-erc721-drop/src/SafeERC721Drop.sol +55 -0
- package/dist/assets/protocols/templates/safe-erc721-drop/test/SafeERC721Drop.t.sol +58 -0
- package/dist/assets/protocols/templates/safe-vesting/README.md +3 -0
- package/dist/assets/protocols/templates/safe-vesting/SECURITY.md +3 -0
- package/dist/assets/protocols/templates/safe-vesting/foundry.toml +11 -0
- package/dist/assets/protocols/templates/safe-vesting/remappings.txt +2 -0
- package/dist/assets/protocols/templates/safe-vesting/src/SafeVesting.sol +19 -0
- package/dist/assets/protocols/templates/safe-vesting/test/SafeVesting.t.sol +72 -0
- package/dist/assets/skills/oracle-desk-product/SKILL.md +1 -1
- package/dist/bin/desk-server.mjs +18 -18
- package/dist/bin/oracle-data-mcp.mjs +6 -6
- package/dist/bin/oracle-route.mjs +3 -3
- package/dist/cli/commands/auth.mjs +2 -2
- package/dist/cli/commands/chat.mjs +88 -88
- package/dist/cli/commands/harness.mjs +5 -5
- package/dist/cli/commands/model.mjs +90 -90
- package/dist/cli/commands/setup.mjs +2 -2
- package/dist/cli/commands/swap.mjs +2 -2
- package/dist/data/desk-data.mjs +14 -14
- package/dist/index.mjs +19 -19
- package/dist/router/index.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -328,6 +328,9 @@ Oracle keeps capability and authorization separate:
|
|
|
328
328
|
- `ORACLE_AUTONOMOUS_TRADING=1` is direct execution for trusted owner-controlled
|
|
329
329
|
local code only. It is never model/agent authority and should not be framed as
|
|
330
330
|
equivalent to the `oracle-signer` agent-process path.
|
|
331
|
+
- `ORACLE_AUTONOMOUS_MINT=1` is a separate switch for fail-closed NFT mint
|
|
332
|
+
one-shots only. It does not unlock swaps, bridges, or perps. Trading does not
|
|
333
|
+
imply mint.
|
|
331
334
|
- A deployment may separately install a same-host, owner-gated EVM executor.
|
|
332
335
|
Oracle must verify that executor before describing bounded EVM execution as
|
|
333
336
|
available. Missing deployment capability means "unavailable here," not
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SafeERC20 template security notice
|
|
2
|
+
|
|
3
|
+
**Not a paid security-firm audit.**
|
|
4
|
+
|
|
5
|
+
This template is:
|
|
6
|
+
- fixed-supply ERC20
|
|
7
|
+
- Ownable2Step + pause
|
|
8
|
+
- no tax / blacklist / upgrade proxy
|
|
9
|
+
- covered by Foundry unit tests in `test/`
|
|
10
|
+
|
|
11
|
+
Before mainnet capital:
|
|
12
|
+
1. Keep or expand Foundry tests green
|
|
13
|
+
2. Run static analysis (Slither/Aderyn) if available
|
|
14
|
+
3. Book an independent Solidity auditor for any real TVL
|
|
15
|
+
|
|
16
|
+
Oracle only prepares unsigned deploys after the JS gate confirms `forge test` passes.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
5
|
+
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
|
|
6
|
+
import {ERC20Pausable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
|
|
7
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
8
|
+
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @title SafeERC20
|
|
12
|
+
* @notice Opinionated fixed-supply ERC20 for Oracle protocol-builder v1.
|
|
13
|
+
*
|
|
14
|
+
* Security posture (templates, NOT a firm audit):
|
|
15
|
+
* - Fixed supply minted once to `initialHolder` in constructor (no hidden mint).
|
|
16
|
+
* - Ownable2Step ownership (no single-tx accidental transfer).
|
|
17
|
+
* - Pausable emergency stop (owner only).
|
|
18
|
+
* - No transfer tax, no blacklist, no max-tx, no trading cooldown.
|
|
19
|
+
* - No upgradeability / no proxy.
|
|
20
|
+
* - Burn optional by holders only (ERC20Burnable).
|
|
21
|
+
*
|
|
22
|
+
* This is a reviewed starter template with Foundry tests. It is NOT a paid
|
|
23
|
+
* security-firm audit. Do not put mainnet TVL on custom forks without one.
|
|
24
|
+
*/
|
|
25
|
+
contract SafeERC20 is ERC20, ERC20Burnable, ERC20Pausable, Ownable2Step {
|
|
26
|
+
constructor(
|
|
27
|
+
string memory name_,
|
|
28
|
+
string memory symbol_,
|
|
29
|
+
uint256 initialSupply,
|
|
30
|
+
address initialHolder,
|
|
31
|
+
address initialOwner
|
|
32
|
+
) ERC20(name_, symbol_) Ownable(initialOwner) {
|
|
33
|
+
require(initialHolder != address(0), "SafeERC20: holder zero");
|
|
34
|
+
require(initialOwner != address(0), "SafeERC20: owner zero");
|
|
35
|
+
require(initialSupply > 0, "SafeERC20: supply zero");
|
|
36
|
+
_mint(initialHolder, initialSupply);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pause() external onlyOwner {
|
|
40
|
+
_pause();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function unpause() external onlyOwner {
|
|
44
|
+
_unpause();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _update(address from, address to, uint256 value)
|
|
48
|
+
internal
|
|
49
|
+
override(ERC20, ERC20Pausable)
|
|
50
|
+
{
|
|
51
|
+
super._update(from, to, value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {SafeERC20} from "../src/SafeERC20.sol";
|
|
6
|
+
|
|
7
|
+
contract SafeERC20Test is Test {
|
|
8
|
+
SafeERC20 internal token;
|
|
9
|
+
address internal owner = makeAddr("owner");
|
|
10
|
+
address internal holder = makeAddr("holder");
|
|
11
|
+
address internal alice = makeAddr("alice");
|
|
12
|
+
address internal bob = makeAddr("bob");
|
|
13
|
+
|
|
14
|
+
uint256 internal constant SUPPLY = 1_000_000 ether;
|
|
15
|
+
|
|
16
|
+
function setUp() public {
|
|
17
|
+
token = new SafeERC20("Safe", "SAFE", SUPPLY, holder, owner);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function test_fixedSupply_noHiddenMint() public view {
|
|
21
|
+
assertEq(token.totalSupply(), SUPPLY);
|
|
22
|
+
assertEq(token.balanceOf(holder), SUPPLY);
|
|
23
|
+
assertEq(token.balanceOf(owner), 0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function test_transfer_noTax() public {
|
|
27
|
+
vm.prank(holder);
|
|
28
|
+
token.transfer(alice, 100 ether);
|
|
29
|
+
assertEq(token.balanceOf(alice), 100 ether);
|
|
30
|
+
assertEq(token.balanceOf(holder), SUPPLY - 100 ether);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function test_pause_blocksTransfers() public {
|
|
34
|
+
vm.prank(owner);
|
|
35
|
+
token.pause();
|
|
36
|
+
vm.prank(holder);
|
|
37
|
+
vm.expectRevert();
|
|
38
|
+
token.transfer(alice, 1 ether);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function test_onlyOwner_canPause() public {
|
|
42
|
+
vm.prank(alice);
|
|
43
|
+
vm.expectRevert();
|
|
44
|
+
token.pause();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function test_ownable2Step_requiresAccept() public {
|
|
48
|
+
vm.prank(owner);
|
|
49
|
+
token.transferOwnership(alice);
|
|
50
|
+
// still owner until accept
|
|
51
|
+
assertEq(token.owner(), owner);
|
|
52
|
+
vm.prank(alice);
|
|
53
|
+
token.acceptOwnership();
|
|
54
|
+
assertEq(token.owner(), alice);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function test_burn_reducesSupply() public {
|
|
58
|
+
vm.prank(holder);
|
|
59
|
+
token.burn(10 ether);
|
|
60
|
+
assertEq(token.totalSupply(), SUPPLY - 10 ether);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function test_rejectZeroHolder() public {
|
|
64
|
+
vm.expectRevert(bytes("SafeERC20: holder zero"));
|
|
65
|
+
new SafeERC20("X", "X", SUPPLY, address(0), owner);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function test_rejectZeroSupply() public {
|
|
69
|
+
vm.expectRevert(bytes("SafeERC20: supply zero"));
|
|
70
|
+
new SafeERC20("X", "X", 0, holder, owner);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
5
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
6
|
+
|
|
7
|
+
contract SafeERC721Drop is ERC721, Ownable {
|
|
8
|
+
uint256 public immutable cap;
|
|
9
|
+
address payable public immutable treasury;
|
|
10
|
+
uint256 public immutable mintPrice;
|
|
11
|
+
uint256 public immutable perWalletCap;
|
|
12
|
+
uint256 public totalMinted;
|
|
13
|
+
mapping(address => uint256) public mintedBy;
|
|
14
|
+
|
|
15
|
+
event Withdrawal(address indexed treasury, uint256 amount);
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
string memory name_,
|
|
19
|
+
string memory symbol_,
|
|
20
|
+
uint256 cap_,
|
|
21
|
+
address treasury_,
|
|
22
|
+
uint256 mintPrice_,
|
|
23
|
+
uint256 perWalletCap_,
|
|
24
|
+
address initialOwner
|
|
25
|
+
) ERC721(name_, symbol_) Ownable(initialOwner) {
|
|
26
|
+
require(cap_ > 0, "cap zero");
|
|
27
|
+
require(treasury_ != address(0), "treasury zero");
|
|
28
|
+
require(mintPrice_ > 0, "price zero");
|
|
29
|
+
require(perWalletCap_ > 0 && perWalletCap_ <= cap_, "wallet cap invalid");
|
|
30
|
+
cap = cap_;
|
|
31
|
+
treasury = payable(treasury_);
|
|
32
|
+
mintPrice = mintPrice_;
|
|
33
|
+
perWalletCap = perWalletCap_;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mint(uint256 quantity) external payable {
|
|
37
|
+
require(quantity > 0, "quantity zero");
|
|
38
|
+
require(totalMinted + quantity <= cap, "collection cap");
|
|
39
|
+
require(mintedBy[msg.sender] + quantity <= perWalletCap, "wallet cap");
|
|
40
|
+
require(msg.value == mintPrice * quantity, "wrong payment");
|
|
41
|
+
mintedBy[msg.sender] += quantity;
|
|
42
|
+
uint256 first = totalMinted + 1;
|
|
43
|
+
totalMinted += quantity;
|
|
44
|
+
for (uint256 tokenId = first; tokenId < first + quantity; tokenId++) {
|
|
45
|
+
_safeMint(msg.sender, tokenId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function withdraw() external onlyOwner {
|
|
50
|
+
uint256 amount = address(this).balance;
|
|
51
|
+
(bool ok,) = treasury.call{value: amount}("");
|
|
52
|
+
require(ok, "withdraw failed");
|
|
53
|
+
emit Withdrawal(treasury, amount);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
import {Test} from "forge-std/Test.sol";
|
|
4
|
+
import {SafeERC721Drop} from "../src/SafeERC721Drop.sol";
|
|
5
|
+
|
|
6
|
+
contract SafeERC721DropTest is Test {
|
|
7
|
+
SafeERC721Drop drop;
|
|
8
|
+
address owner = makeAddr("owner");
|
|
9
|
+
address treasury = makeAddr("treasury");
|
|
10
|
+
address alice = makeAddr("alice");
|
|
11
|
+
|
|
12
|
+
function setUp() public {
|
|
13
|
+
drop = new SafeERC721Drop("Safe", "SAFE", 3, treasury, 1 ether, 2, owner);
|
|
14
|
+
vm.deal(alice, 10 ether);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function testMintPaymentAndWalletCap() public {
|
|
18
|
+
vm.prank(alice);
|
|
19
|
+
drop.mint{value: 2 ether}(2);
|
|
20
|
+
assertEq(drop.balanceOf(alice), 2);
|
|
21
|
+
vm.prank(alice);
|
|
22
|
+
vm.expectRevert("wallet cap");
|
|
23
|
+
drop.mint{value: 1 ether}(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function testRejectsWrongPayment() public {
|
|
27
|
+
vm.prank(alice);
|
|
28
|
+
vm.expectRevert("wrong payment");
|
|
29
|
+
drop.mint{value: 0.5 ether}(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function testCollectionCap() public {
|
|
33
|
+
address bob = makeAddr("bob");
|
|
34
|
+
vm.deal(bob, 2 ether);
|
|
35
|
+
vm.prank(alice);
|
|
36
|
+
drop.mint{value: 2 ether}(2);
|
|
37
|
+
vm.prank(bob);
|
|
38
|
+
drop.mint{value: 1 ether}(1);
|
|
39
|
+
address carol = makeAddr("carol");
|
|
40
|
+
vm.deal(carol, 1 ether);
|
|
41
|
+
vm.prank(carol);
|
|
42
|
+
vm.expectRevert("collection cap");
|
|
43
|
+
drop.mint{value: 1 ether}(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function testOnlyOwnerWithdrawsToTreasury() public {
|
|
47
|
+
vm.prank(alice);
|
|
48
|
+
drop.mint{value: 1 ether}(1);
|
|
49
|
+
vm.prank(alice);
|
|
50
|
+
vm.expectRevert();
|
|
51
|
+
drop.withdraw();
|
|
52
|
+
uint256 beforeBalance = treasury.balance;
|
|
53
|
+
vm.prank(owner);
|
|
54
|
+
drop.withdraw();
|
|
55
|
+
assertEq(treasury.balance, beforeBalance + 1 ether);
|
|
56
|
+
assertEq(address(drop).balance, 0);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# safe-vesting
|
|
2
|
+
|
|
3
|
+
Fixed wrapper around OpenZeppelin Contracts 5.0.2 `VestingWallet`, with beneficiary, start, and duration fixed at construction. The beneficiary remains the OpenZeppelin owner/recipient identity, but ownership transfer and renunciation are permanently disabled. No separate administrator or proxy authority.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# SafeVesting security notice
|
|
2
|
+
|
|
3
|
+
**Not a paid security-firm audit.** This is a fixed OpenZeppelin VestingWallet wrapper. Beneficiary, start, and duration are constructor-set. The beneficiary is the owner/recipient identity required by `VestingWallet`, and the wrapper disables ownership transfer and renunciation. There is no separate administrator, proxy, upgrade, or delegatecall.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
import {VestingWallet} from "@openzeppelin/contracts/finance/VestingWallet.sol";
|
|
4
|
+
|
|
5
|
+
contract SafeVesting is VestingWallet {
|
|
6
|
+
error BeneficiaryImmutable();
|
|
7
|
+
|
|
8
|
+
constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds)
|
|
9
|
+
VestingWallet(beneficiary, startTimestamp, durationSeconds)
|
|
10
|
+
{}
|
|
11
|
+
|
|
12
|
+
function transferOwnership(address) public pure override {
|
|
13
|
+
revert BeneficiaryImmutable();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renounceOwnership() public pure override {
|
|
17
|
+
revert BeneficiaryImmutable();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
import {Test} from "forge-std/Test.sol";
|
|
4
|
+
import {SafeVesting} from "../src/SafeVesting.sol";
|
|
5
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
6
|
+
|
|
7
|
+
contract TestToken is ERC20 {
|
|
8
|
+
constructor() ERC20("Test", "TEST") {}
|
|
9
|
+
|
|
10
|
+
function mint(address to, uint256 amount) external {
|
|
11
|
+
_mint(to, amount);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
contract SafeVestingTest is Test {
|
|
16
|
+
SafeVesting vesting;
|
|
17
|
+
address beneficiary = makeAddr("beneficiary");
|
|
18
|
+
uint64 start;
|
|
19
|
+
|
|
20
|
+
function setUp() public {
|
|
21
|
+
start = uint64(block.timestamp + 100);
|
|
22
|
+
vesting = new SafeVesting(beneficiary, start, 1000);
|
|
23
|
+
vm.deal(address(vesting), 10 ether);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function testFixedScheduleAndBeneficiary() public view {
|
|
27
|
+
assertEq(vesting.owner(), beneficiary);
|
|
28
|
+
assertEq(vesting.start(), start);
|
|
29
|
+
assertEq(vesting.duration(), 1000);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function testBeneficiaryCannotBeChangedOrRenounced() public {
|
|
33
|
+
vm.startPrank(beneficiary);
|
|
34
|
+
vm.expectRevert(SafeVesting.BeneficiaryImmutable.selector);
|
|
35
|
+
vesting.transferOwnership(makeAddr("replacement"));
|
|
36
|
+
vm.expectRevert(SafeVesting.BeneficiaryImmutable.selector);
|
|
37
|
+
vesting.renounceOwnership();
|
|
38
|
+
vm.stopPrank();
|
|
39
|
+
assertEq(vesting.owner(), beneficiary);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function testLinearReleaseSemantics() public {
|
|
43
|
+
vesting.release();
|
|
44
|
+
assertEq(beneficiary.balance, 0);
|
|
45
|
+
vm.warp(start + 500);
|
|
46
|
+
vesting.release();
|
|
47
|
+
assertEq(beneficiary.balance, 5 ether);
|
|
48
|
+
vm.warp(start + 1000);
|
|
49
|
+
vesting.release();
|
|
50
|
+
assertEq(beneficiary.balance, 10 ether);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function testAnyoneCanTriggerButOnlyBeneficiaryReceives() public {
|
|
54
|
+
vm.warp(start + 1000);
|
|
55
|
+
address caller = makeAddr("caller");
|
|
56
|
+
vm.prank(caller);
|
|
57
|
+
vesting.release();
|
|
58
|
+
assertEq(caller.balance, 0);
|
|
59
|
+
assertEq(beneficiary.balance, 10 ether);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function testERC20ReleaseUsesSameSchedule() public {
|
|
63
|
+
TestToken token = new TestToken();
|
|
64
|
+
token.mint(address(vesting), 1000 ether);
|
|
65
|
+
vm.warp(start + 250);
|
|
66
|
+
vesting.release(address(token));
|
|
67
|
+
assertEq(token.balanceOf(beneficiary), 250 ether);
|
|
68
|
+
vm.warp(start + 1000);
|
|
69
|
+
vesting.release(address(token));
|
|
70
|
+
assertEq(token.balanceOf(beneficiary), 1000 ether);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -97,7 +97,7 @@ does not prove npm users receive it. A locally hosted model/data/policy loop doe
|
|
|
97
97
|
a bundled signer. A merged feature is not public until the npm artifact containing it is
|
|
98
98
|
published and inspected.
|
|
99
99
|
|
|
100
|
-
**Current verified state as of 2026-08-13:** public npm `@oracle-agent/oracle@0.24.
|
|
100
|
+
**Current verified state as of 2026-08-13:** public npm `@oracle-agent/oracle@0.24.5`
|
|
101
101
|
ships the optional encrypted local vault + short-lived loopback signer. Hosted/default
|
|
102
102
|
Oracle stays prepare-only. Self-host after `oracle sign init|import` is **auto-armed**
|
|
103
103
|
for user-initiated actions. Public still charges the disclosed fee card; Administrator
|