Install-free operator CLI on npm:
npx programmable-secret helpProgrammable Secrets is a receipt-backed entitlement protocol for finance-agent workflows. Providers commit encrypted market data, issuer materials, research, or private tool access onchain. Buyers satisfy a dataset policy in the shared vault, mint an access receipt, and then request the buyer-bound key envelope from the offchain key release service.
This package is the contract source of truth for:
PolicyVaultPaymentModuleAccessReceipt- built-in policy evaluator modules
- example custom policy evaluator modules
- external
IdentityRegistryintegration for UAID-gated policies - deployment automation
- checked-in ABIs
- testnet deployment manifests
- Repository map:
docs/repository-structure.md - Hackathon repo map:
docs/hackathon-repo-map.md - Subgraph package:
subgraph/README.md - Deployments source of truth:
deployments/ - Operator CLI entrypoint:
script/manage-policies.mjs
| Path | Purpose |
|---|---|
src/ |
Solidity contracts and evaluator modules. |
test/ |
Foundry tests for protocol behavior, upgrades, and security invariants. |
script/ |
Deploy, verify, and operator CLI code. |
__tests__/ |
Node CLI tests that validate command behavior and serialization. |
abis/ |
Checked-in app-facing ABI artifacts consumed by other repos. |
deployments/ |
Canonical live deployment manifests for supported testnets. |
subgraph/ |
The Graph indexer package and Kubernetes deployment manifests. |
examples/ |
End-to-end sample payloads and policy templates. |
stylus/ |
Stylus custom evaluator implementations. |
docs/ |
Navigation and cross-repo integration references. |
The hackathon build spans multiple repositories. This contracts repository is the onchain source of truth and is linked to the following components:
| Repository | Role in the system |
|---|---|
hashgraph-online/programmable-secrets-fe |
User-facing frontend for provider and buyer workflows. |
hashgraph-online/programmable-secrets-skill |
HCS-26 operator skill package used by agents and CLI-driven flows. |
erc-8004/erc-8004-contracts |
Upstream ERC-8004 registry contracts used for identity-gated policy checks. |
For a full integration matrix and ownership boundaries, see docs/hackathon-repo-map.md.
| Contract | Responsibility |
|---|---|
PolicyVault |
Shared registry for provider-owned datasets plus attached policies. Each policy stores a list of registered policy evaluator modules and immutable condition config bytes. |
PaymentModule |
Validates purchase conditions by calling each evaluator registered on the selected policy, settles native ETH, mints the access receipt, and resolves active entitlement state. |
AccessReceipt |
ERC-721 entitlement proving a buyer satisfied a specific dataset policy. Transferability is fixed per policy at mint time. |
TimeRangeCondition |
Built-in policy evaluator that enforces notBefore / notAfter purchase windows. |
UaidOwnershipCondition |
Built-in policy evaluator that enforces ERC-8004 wallet ownership plus exact buyer UAID match. |
AddressAllowlistCondition |
Built-in policy evaluator that enforces a provider-supplied wallet allowlist. |
ThresholdCommitteeCondition |
Example Stylus evaluator that verifies a quorum of committee attestations before purchase. |
external IdentityRegistry |
ERC-8004 registry address referenced by UaidOwnershipCondition to prove wallet ownership of a target HCS-14 UAID-native agent onchain. |
The intended app entrypoints are:
- dataset registration and policy creation through
PolicyVault - purchase through
PaymentModule - proof checks through
AccessReceiptorPaymentModule.receiptOfPolicyAndBuyer - custom policy module registration through
PolicyVault.registerPolicyEvaluator
| Network | Chain ID | RPC | Explorer |
|---|---|---|---|
| Robinhood Chain Testnet | 46630 |
https://rpc.testnet.chain.robinhood.com/rpc |
https://explorer.testnet.chain.robinhood.com |
| Arbitrum Sepolia | 421614 |
https://sepolia-rollup.arbitrum.io/rpc |
https://sepolia.arbiscan.io |
Robinhood testnet is the primary operator target. Arbitrum Sepolia is maintained as a secondary testnet deployment target.
Canonical app-facing addresses are tracked in:
deployments/robinhood-testnet.jsondeployments/arbitrum-sepolia.json
Current deployed addresses:
| Network | PolicyVault | PaymentModule | AccessReceipt | IdentityRegistry |
|---|---|---|---|---|
| Robinhood Chain Testnet | 0x073fc3fE9B2c00E470199550870D458D13421614 |
0x5b4a056d2203C5940257635F073A253B958ba43c |
0x4Aa65779ce3dF24E5EeC7a786721765dF50a106b |
0x0000000000000000000000000000000000000000 |
| Arbitrum Sepolia | 0x600b2326537f74E7d0fD4A6e59B00FA6E6b63536 |
0x7F17cB0Ec2e8981A6489Ec1281C55474e575a66D |
0x157Ec116169815ab15079dC117854fe19f96d51c |
0x8004A818BFB912233c491871b3d84c89A494BD9e |
Built-in policy evaluators are also deployed and recorded in each manifest:
TimeRangeConditionUaidOwnershipConditionAddressAllowlistCondition
The contract repo should always treat these manifest files as the deployment source of truth.
Primary functions:
initialize(address initialOwner)registerPolicyEvaluator(address evaluator, bytes32 metadataHash)payableregisterBuiltInEvaluator(address evaluator, bytes32 metadataHash)owner-onlysetPolicyEvaluatorActive(address evaluator, bool active)owner-onlysetEvaluatorRegistrationFee(uint256 newFee)owner-onlysetEvaluatorFeeRecipient(address newFeeRecipient)owner-onlyregisterDataset(bytes32 ciphertextHash, bytes32 keyCommitment, bytes32 metadataHash, bytes32 providerUaidHash)setDatasetActive(uint256 datasetId, bool active)createPolicyForDataset(uint256 datasetId, address payout, address paymentToken, uint96 price, bool receiptTransferable, bytes32 metadataHash, PolicyConditionInput[] conditions)updatePolicy(uint256 policyId, uint96 newPrice, bool active, bytes32 newMetadataHash)getDataset(uint256 datasetId)getDatasetPolicyCount(uint256 datasetId)getDatasetPolicyIdAt(uint256 datasetId, uint256 index)getDatasetPolicyIds(uint256 datasetId)getPolicy(uint256 policyId)getPolicyConditionCount(uint256 policyId)getPolicyCondition(uint256 policyId, uint256 index)getPolicyEvaluator(address evaluator)datasetCount()policyCount()
Events:
PolicyEvaluatorRegisteredPolicyEvaluatorStatusUpdatedPolicyEvaluatorFeeUpdatedPolicyEvaluatorFeeRecipientUpdatedDatasetRegisteredDatasetStatusUpdatedPolicyCreatedPolicyUpdated
Primary functions:
initialize(address initialOwner, address policyVaultAddress, address accessReceiptAddress)purchase(uint256 policyId, address recipient, bytes[] conditionRuntimeInputs)hasAccess(uint256 policyId, address buyer)hasDatasetAccess(uint256 datasetId, address buyer)receiptOfPolicyAndBuyer(uint256 policyId, address buyer)setPolicyVault(address policyVaultAddress)setAccessReceipt(address accessReceiptAddress)
Events:
AccessGrantedPolicyVaultUpdatedAccessReceiptUpdated
Primary functions:
constructor(address initialOwner)setPaymentModule(address newPaymentModule)mintReceipt(address buyer, address recipient, uint256 policyId, uint256 datasetId, address paymentToken, uint96 price, uint64 purchasedAt, bool receiptTransferable, bytes32 ciphertextHash, bytes32 keyCommitment)hasAccess(uint256 policyId, address buyer)receiptOfPolicyAndBuyer(uint256 policyId, address buyer)getReceipt(uint256 receiptTokenId)
Events:
ReceiptMintedPaymentModuleUpdated
- Datasets are first-class registry entries. A provider registers an encrypted dataset once, then attaches one or more policies to it.
- Policies are explorable onchain through
datasetCount,policyCount,getDataset,getPolicy,getDatasetPolicyCount,getDatasetPolicyIdAt, andgetDatasetPolicyIds. - Native ETH only in the current green path. Policies with a non-zero
paymentTokenrevert. PolicyVaultis a shared registry, not a per-provider custom vault factory.- A policy is generic: it stores an ordered list of evaluator contracts plus opaque
configDatafor each condition. - Each evaluator must be registered before providers can attach it to a policy.
- Public evaluator registration costs
0.05 ETHby default and pays that fee toevaluatorFeeRecipient. - Built-in evaluator registration is owner-only and fee-free.
PaymentModule.purchaseloops over the selected policy’s stored evaluator list and passes the caller-suppliedconditionRuntimeInputs[index]to each evaluator.PolicyVaultowns dataset and policy metadata plus provider-controlled mutability.PaymentModuleis the only contract allowed to mint receipts.- One wallet can hold at most one active receipt per policy and per dataset.
- Receipt transferability is decided when the policy is created and is immutable for receipts minted from that policy.
- Non-transferable receipts stay buyer-bound.
- Transferable receipts move active access with the token and cannot be transferred to a wallet that already has access to the same dataset.
- Every receipt token resolves to the same metadata URI:
ipfs://bafkreibw3osbcrk7w522tcjuz5a4ihffd3bfbjkwmfso5esxyfml2cfal4. - Condition modules own their own validation rules. For example,
TimeRangeConditiontreatsnotAfter == block.timestampas expired. - UAID-bound purchases pass the exact buyer UAID string in the runtime input consumed by
UaidOwnershipCondition. - Evaluators are enforced at purchase time.
PaymentModule.hasAccessandhasDatasetAccessresolve durable entitlement by checking receipt existence plus current policy and dataset active state. - Time range conditions govern whether a purchase can happen, not a post-purchase streaming lease. A provider can still deactivate the dataset or policy to revoke active access resolution.
AccessReceiptremains the durable historical proof of purchase, whilePaymentModule.hasDatasetAccessresolves whether any currently active policy on a dataset still maps to the buyer's receipt.- Allowlist enforcement is onchain through
AddressAllowlistCondition. - Offchain key release should validate both the signed buyer request and current onchain access state.
- Older deployments may not expose evaluator index helper reads (
getPolicyEvaluatorCount,getPolicyEvaluatorAt). The CLI falls back to manifest discovery when those helpers are unavailable.
The default deployment registers three built-ins:
| Module | Config type | Runtime input |
|---|---|---|
TimeRangeCondition |
TimeRangeConfig { notBefore, notAfter } |
empty bytes |
UaidOwnershipCondition |
UaidOwnershipConfig { requiredBuyerUaidHash, identityRegistry, agentId } |
ABI-encoded buyer UAID string |
AddressAllowlistCondition |
ABI-encoded address[] |
empty bytes |
Custom modules can implement IPolicyCondition and register themselves through registerPolicyEvaluator(...) after paying the fee.
When creating a policy, set receiptTransferable = true only if you want the ERC-721 itself to carry the live entitlement between wallets.
For most data-sales flows, leave it false so access stays tied to the original buyer.
The repo includes a concrete custom module at src/EthBalanceCondition.sol.
It enforces a single rule: the buyer wallet must hold at least the configured minimumBalanceWei at purchase time.
End-to-end operator flow:
- Deploy the evaluator:
forge create src/EthBalanceCondition.sol:EthBalanceCondition \
--rpc-url $RPC_URL \
--private-key $ETH_PK_2- Register it in
PolicyVaultand pay the public0.05 ETHevaluator fee:
cast send $POLICY_VAULT \
"registerPolicyEvaluator(address,bytes32)" \
$EVALUATOR_ADDRESS \
$(cast keccak "eth-balance-threshold-v1") \
--value 0.05ether \
--rpc-url $RPC_URL \
--private-key $ETH_PK_2- Create the encrypted dataset bundle and register the dataset through the CLI:
programmable-secret krs encrypt \
--plaintext-file ./examples/two-agent-sale/agent-a-signal.json \
--title "ETH balance gated signal" \
--provider-uaid "uaid:did:pkh:eip155:46630:0x1111111111111111111111111111111111111111;nativeId=eip155:46630:0x1111111111111111111111111111111111111111" \
--output ./examples/custom-evaluators/eth-balance-bundle.json
programmable-secret datasets register \
--wallet provider \
--bundle-file ./examples/custom-evaluators/eth-balance-bundle.json- Encode the threshold config (
0.1 ETH) and import the custom policy from the checked-in template:
export CONFIG_DATA=$(cast abi-encode "f(uint256)" 100000000000000000)
jq --arg evaluator "$EVALUATOR_ADDRESS" \
--arg config "$CONFIG_DATA" \
--argjson datasetId <dataset-id> \
'.policy.datasetId = $datasetId
| .policy.conditions[0].evaluator = $evaluator
| .policy.conditions[0].configData = $config' \
./examples/custom-evaluators/eth-balance-policy.template.json \
> /tmp/eth-balance-policy.json
programmable-secret policies import \
--wallet provider \
--file /tmp/eth-balance-policy.jsonTo make that policy transferable, set "receiptTransferable": true in the imported JSON or pass --receipt-transferable true when using policies create-timebound or policies create-uaid.
- Buyer purchases and proves the unlock:
programmable-secret purchase --policy-id <policy-id> --wallet agent
programmable-secret receipts get --receipt-id <receipt-id>
programmable-secret krs verify \
--bundle-file ./examples/custom-evaluators/eth-balance-bundle.json \
--policy-id <policy-id> \
--receipt-id <receipt-id> \
--buyer <buyer-wallet>Proof in this repo:
test/ProgrammableSecretsCustomEvaluator.t.solproves evaluator registration works- it proves a buyer above the threshold can purchase successfully
- it proves a buyer below the threshold reverts with
PolicyConditionFailed(0) - it proves zero-threshold configs are rejected
The repo also includes a Stylus evaluator in stylus/threshold-committee-condition.
It verifies a quorum of committee signatures over:
- evaluator address
- policy vault address
- chain id
- policy id
- buyer
- recipient
- policy context hash
- attestation deadline
The checked-in app-facing ABI is abis/ThresholdCommitteeCondition.abi.json.
Local build and RPC validation:
pnpm run stylus:fmt
pnpm run stylus:test
pnpm run stylus:clippy
pnpm run stylus:build
pnpm run stylus:checkEnd-to-end operator flow on Arbitrum Sepolia:
- Deploy the Stylus evaluator:
cd stylus/threshold-committee-condition
cargo stylus deploy \
--endpoint https://sepolia-rollup.arbitrum.io/rpc \
--private-key-path ./.keys/provider.key \
--max-fee-per-gas-gwei 1- Register it through
PolicyVaultwith the public0.05 ETHevaluator fee:
programmable-secret evaluators register \
--network arbitrum-sepolia \
--wallet provider \
--evaluator $EVALUATOR_ADDRESS \
--metadata-json '{"name":"threshold-committee-condition","implementation":"stylus","kind":"threshold-committee"}'- Encrypt and register the dataset:
programmable-secret krs encrypt \
--plaintext-file ./examples/two-agent-sale/agent-a-signal.json \
--title "Threshold committee signal" \
--provider-uaid "uaid:did:pkh:eip155:46630:0x1111111111111111111111111111111111111111;nativeId=eip155:46630:0x1111111111111111111111111111111111111111" \
--output ./examples/custom-evaluators/threshold-committee-bundle.json
programmable-secret datasets register \
--network arbitrum-sepolia \
--wallet provider \
--bundle-file ./examples/custom-evaluators/threshold-committee-bundle.json- Build the condition config and import the policy:
programmable-secret attestations threshold-config \
--policy-context-text "committee-release-v1" \
--max-duration-minutes 60 \
--threshold 2 \
--committee 0xSigner1,0xSigner2,0xSigner3 \
--output /tmp/threshold-committee-config.json
jq --arg evaluator "$EVALUATOR_ADDRESS" \
--argjson datasetId <dataset-id> \
--slurpfile cfg /tmp/threshold-committee-config.json \
'.policy.datasetId = $datasetId
| .policy.conditions[0].evaluator = $evaluator
| .policy.conditions[0].configData = $cfg[0].configData' \
./examples/custom-evaluators/threshold-committee-policy.template.json \
> /tmp/threshold-committee-policy.json
programmable-secret policies import \
--network arbitrum-sepolia \
--wallet provider \
--file /tmp/threshold-committee-policy.json- Generate runtime attestations, purchase, and verify access:
programmable-secret attestations threshold-runtime \
--network arbitrum-sepolia \
--policy-id <policy-id> \
--buyer <buyer-wallet> \
--evaluator $EVALUATOR_ADDRESS \
--policy-context-text "committee-release-v1" \
--duration-minutes 15 \
--committee-private-keys-file ./committee-signers.local.json \
--output /tmp/threshold-committee-runtime.json
programmable-secret purchase \
--network arbitrum-sepolia \
--policy-id <policy-id> \
--wallet agent \
--runtime-inputs-file /tmp/threshold-committee-runtime.json
programmable-secret receipts get \
--network arbitrum-sepolia \
--receipt-id <receipt-id>
programmable-secret access policy \
--network arbitrum-sepolia \
--policy-id <policy-id> \
--buyer <buyer-wallet>Proof in this repo:
__tests__/threshold-committee-cli.test.mjscovers the CLI config/runtime encoders- the checked-in
custom-threshold-committee-policyexample exercises the full command flow - the evaluator was live-tested on Arbitrum Sepolia by deploying it, registering it through the CLI, importing a policy, generating runtime attestations, and purchasing the policy end to end
PolicyVaultis deployed behindERC1967Proxyusing UUPS.PaymentModuleis deployed behindERC1967Proxyusing UUPS.AccessReceiptis currently a direct deployment, not a proxy.
Operational guidance:
- Treat proxy addresses as canonical app-facing addresses for
PolicyVaultandPaymentModule. AccessReceiptis app-facing directly at its deployed address.- The deploy script initializes proxies with the deployer first, wires
AccessReceiptto the deployedPaymentModule, and optionally starts ownership handoff toCONTRACT_OWNER.
Install dependencies first:
pnpm installRun the Solidity suite:
forge fmt --check
forge lint
forge build --sizes --skip script
forge test -vvvRun the CLI suite:
pnpm run help
pnpm run test:cliRun the Stylus suite:
pnpm run stylus:fmt
pnpm run stylus:test
pnpm run stylus:clippy
pnpm run stylus:build
pnpm run stylus:checkRegenerate checked-in ABIs:
forge inspect --json AccessReceipt abi > abis/AccessReceipt.abi.json
forge inspect --json AddressAllowlistCondition abi > abis/AddressAllowlistCondition.abi.json
forge inspect --json EthBalanceCondition abi > abis/EthBalanceCondition.abi.json
forge inspect --json PaymentModule abi > abis/PaymentModule.abi.json
forge inspect --json PolicyVault abi > abis/PolicyVault.abi.json
forge inspect --json TimeRangeCondition abi > abis/TimeRangeCondition.abi.json
forge inspect --json UaidOwnershipCondition abi > abis/UaidOwnershipCondition.abi.json
cd stylus/threshold-committee-condition && cargo stylus export-abiThe contracts repo now ships a modular CLI under script/cli/ with a thin entrypoint at script/manage-policies.mjs.
The live workflow commands are:
flow:directruns the default Robinhood marketplace path: dataset registration, timebound policy creation, purchase, and local unlock verificationflow:uaidruns the direct onchain ERC-8004 path on a chain with a liveIdentityRegistryflow:brokerregisters through the local Registry Broker withRegistryBrokerClient, then proves the same UAID-gated purchase flow against the selected liveIdentityRegistry
The CLI also ships a concrete two-agent walkthrough:
programmable-secret examples show --name two-agent-saleThat example uses the checked-in fixture at examples/two-agent-sale/agent-a-signal.json and demonstrates:
- Agent A encrypting and packaging the dataset bundle with
krs encrypt - Agent A registering the dataset directly from the bundle via
datasets register --bundle-file ... - Agent A creating a sell-side policy
- Agent B purchasing the policy and reading the minted receipt
- Agent B verifying and decrypting the bundle locally
There is also a custom evaluator walkthrough:
programmable-secret examples show --name custom-eth-balance-policyAnd a Stylus threshold committee walkthrough:
programmable-secret examples show --name custom-threshold-committee-policyThe package also exposes a first-class binary:
programmable-secret <command>Compatibility alias:
programmable-secrets <command>Inside this repo, the equivalent local wrapper is:
pnpm run cli -- <command>Primary install-free entrypoint from npm:
npx programmable-secret helpRelease automation:
- GitHub release tags must match the package version, for example
v0.2.0 .github/workflows/publish-cli.ymlvalidates the package, smoke-tests the tarball, and publishes to npm with provenance- manual dry-runs are available through the
Publish CLI Packageworkflow dispatch
Install the single Node dependency:
pnpm installCreate a local env file:
cp .env.example .envRequired environment variables in .env:
ETH_PKfor the agent wallet that will register the ERC-8004 identity and purchase accessETH_PK_2for the provider wallet that will register the dataset and create the gated policy
Show the available commands:
pnpm run help
npx programmable-secret helpStart with the guided entrypoint:
programmable-secret init
programmable-secret startCheck readiness before a live run:
programmable-secret doctorIf wallet or broker keys are missing, bootstrap a local env file from the running Docker broker:
programmable-secret env-bootstrapThis writes .env.local when it does not already exist and can pull common workflow keys from a locally running broker container when available.
Set PROGRAMMABLE_SECRETS_ENV_OUTPUT_PATH if you want to generate a different file.
Run the direct identity flow:
programmable-secret flow:directWhat the direct flow does on the selected live ERC-8004 network:
- registers a new agent in the external ERC-8004
IdentityRegistry - registers a dataset in
PolicyVault - creates a UAID-gated policy that only that registered agent can unlock
- purchases the policy from the registered agent wallet through
PaymentModule - reads back the minted
AccessReceipt - decrypts the locally prepared payload to prove the unlock path completed
Run the Registry Broker-backed flow:
programmable-secret flow:brokerWhat the broker-backed flow does:
- starts a local agent endpoint with the
standards-sdk - registers that agent in the local Registry Broker with
RegistryBrokerClient - links the agent into the selected live ERC-8004 registry and receives a real UAID
- registers a dataset in
PolicyVault - creates a UAID-gated policy that only that broker-issued UAID can unlock
- purchases the policy, reads back the minted
AccessReceipt, and decrypts the prepared payload
Optional overrides:
PROGRAMMABLE_SECRETS_NETWORKPROGRAMMABLE_SECRETS_AGENT_URIPROGRAMMABLE_SECRETS_BUYER_UAIDPROGRAMMABLE_SECRETS_PROVIDER_UAIDPROGRAMMABLE_SECRETS_PRICE_WEIPROGRAMMABLE_SECRETS_EXPIRES_AT_UNIXREGISTRY_BROKER_BASE_URLREGISTRY_BROKER_API_KEYREGISTRY_BROKER_ACCOUNT_IDREGISTRY_BROKER_ERC8004_NETWORK
REGISTRY_BROKER_BASE_URL defaults to https://hol.org/registry/api/v1.
Only override it when you are intentionally targeting a non-production broker for local development.
flow:direct defaults to Robinhood testnet.
flow:uaid and flow:broker should target Arbitrum Sepolia unless your selected network manifest has a live IdentityRegistry.
If you want to point at a different env file, set PROGRAMMABLE_SECRETS_ENV_PATH before running the command.
Global operator flags:
--jsonfor machine-readable output--previeworpreview <command>to inspect state-changing calls before sending them--interactiveto prompt for missing required options--profile <name>to load a named operator profile--agent-safeto enable JSON-first, quiet, non-interactive execution defaults
The CLI now covers the full operator surface around the deployed contracts.
Read-only commands:
programmable-secret contracts
programmable-secret contracts --agent-safe
programmable-secret help --json
programmable-secret policies evaluators
programmable-secret datasets list
programmable-secret datasets get --dataset-id 1
programmable-secret datasets export --dataset-id 1 --output dataset-1.json
programmable-secret policies list
programmable-secret policies get --policy-id 1
programmable-secret policies export --policy-id 1 --output policy-1.json
programmable-secret access policy --policy-id 1 --buyer 0x...
programmable-secret access dataset --dataset-id 1 --buyer 0x...
programmable-secret receipts get --receipt-id 1Write commands:
programmable-secret identity register --agent-uri https://hol.org/agents/volatility-trading-agent-custodian
programmable-secret datasets register --provider-uaid "uaid:did:pkh:eip155:46630:0x1111111111111111111111111111111111111111;nativeId=eip155:46630:0x1111111111111111111111111111111111111111" --metadata-json '{"title":"TSLA feed"}' --ciphertext "encrypted payload" --key-material "wrapped key"
programmable-secret datasets import --file dataset-1.json
programmable-secret datasets set-active --dataset-id 1 --active false
programmable-secret policies create-timebound --dataset-id 1 --price-eth 0.00001 --duration-hours 24 --metadata-json '{"title":"24 hour access"}'
programmable-secret policies create-uaid --dataset-id 1 --price-eth 0.00001 --duration-hours 24 --required-buyer-uaid uaid:aid:... --agent-id 97
programmable-secret policies import --file policy-1.json
programmable-secret policies update --policy-id 1 --price-eth 0.00002 --active true --metadata-json '{"title":"Updated access"}'
programmable-secret purchase --policy-id 1policies allowlist is intentionally immutable in the evaluator-array model. Recreate the policy with a new allowlist config and deactivate the prior policy.
The CLI accepts either direct hashes or operator-friendly raw inputs for dataset registration:
--ciphertext-hashor--ciphertext--key-commitmentor--key-material--metadata-hash,--metadata-json,--metadata-file, or--metadata--provider-uaid-hashor--provider-uaid
Wallet selection:
- provider-facing write commands default to
ETH_PK_2 - agent-facing commands default to
ETH_PK - override with
--wallet provideror--wallet agent
Bootstrap a local config with named profiles:
programmable-secret init
programmable-secret profiles list
programmable-secret profiles show --profile robinhood-agentUse built-in templates to scaffold finance-agent flows:
programmable-secret templates list
programmable-secret templates show --name finance-timebound-dataset
programmable-secret templates write --name finance-uaid-policy --output finance-uaid-policy.jsonGenerate shell completions:
programmable-secret completions zsh --output ~/.zsh/completions/_programmable-secret
programmable-secret completions bash
programmable-secret completions fishThe CLI now includes local bundle tooling for operator previews and end-to-end verification:
programmable-secret krs encrypt --plaintext '{"signal":"buy","market":"TSLA"}' --output bundle.json
programmable-secret krs verify --bundle-file bundle.json --policy-id 1 --buyer 0x...
programmable-secret krs decrypt --bundle-file bundle.jsonThese commands are local-only helpers. They are meant for operator testing, payload preparation, and buyer-side verification, not for production secret custody.
Required environment variables for script/Deploy.s.sol:
ETH_PKDEPLOYER_ADDRESSCONTRACT_OWNER
Example deploy command:
forge script script/Deploy.s.sol:Deploy \
--rpc-url https://rpc.testnet.chain.robinhood.com/rpc \
--broadcast -vvvThe GitHub workflow .github/workflows/deploy-arbitrum-sepolia.yml performs the same deployment flow for both supported testnets and writes a structured manifest to:
deployments/robinhood-testnet.jsondeployments/arbitrum-sepolia.json
The repo also includes .github/workflows/deploy-threshold-committee-evaluator.yml for manual Stylus evaluator deployment to:
arbitrum-sepoliarobinhood-testnet
Each manifest is expected to record:
contracts.policyVault.proxyAddresscontracts.policyVault.implementationAddresscontracts.paymentModule.proxyAddresscontracts.paymentModule.implementationAddresscontracts.accessReceipt.addressentrypoints.policyVaultAddressentrypoints.paymentModuleAddressentrypoints.accessReceiptAddress
The current checked-in manifests reflect live deployments and may not match across Robinhood Chain Testnet and Arbitrum Sepolia. If you need deterministic same-address deployment, use the CREATE2 path below and redeploy both networks in lockstep.
If you need to reproduce the same deployment pattern on a fresh environment, use the CREATE2-based deployment path:
- keep
DEPLOYER_ADDRESSandCONTRACT_OWNERidentical on both networks - keep the CREATE2 salts identical on both networks
- run Foundry with the standard CREATE2 deployer at
0x4e59b44847b379578588920cA78FbF26c0B4956C
Recommended command shape:
forge script script/Deploy.s.sol:DeployCreate2 \
--rpc-url https://rpc.testnet.chain.robinhood.com/rpc \
--broadcast \
--always-use-create-2-factory \
--create2-deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C \
-vvvDefault salts are baked into the deploy script:
programmable-secrets-policy-vault-implementation-v1programmable-secrets-policy-vault-proxy-v1programmable-secrets-payment-module-implementation-v1programmable-secrets-payment-module-proxy-v1programmable-secrets-access-receipt-v1
Because Robinhood Chain Testnet and Arbitrum Sepolia both expose the standard 0x4e59...956C CREATE2 deployer, deterministic redeployment to matching addresses is feasible. The manifests should only be updated after both networks are redeployed in CREATE2 mode.
Checked-in app-facing ABIs:
abis/PolicyVault.abi.jsonabis/PaymentModule.abi.jsonabis/AccessReceipt.abi.jsonabis/ThresholdCommitteeCondition.abi.json
These are the files the broker and portal should consume for code generation, client reads, and transaction encoding.
This repo now includes a Graph subgraph package at subgraph/.
It indexes:
PolicyVaultPaymentModuleAccessReceipt
Network manifests are generated directly from the deployment source of truth:
deployments/robinhood-testnet.json(default)deployments/arbitrum-sepolia.json(optional)
Build locally:
pnpm --dir subgraph install
pnpm --dir subgraph run buildGenerated manifests:
subgraph/subgraph.robinhood-testnet.yamlsubgraph/subgraph.arbitrum-sepolia.yaml
See subgraph/README.md for deployment commands and entity coverage.
Workflows:
Foundry CISolidity SecurityDeploy EVM TestnetDeploy Threshold Committee Evaluator
Foundry CI also runs the packaged CLI smoke path plus the Stylus validation pipeline:
pnpm run test:clipnpm run stylus:fmtpnpm run stylus:testpnpm run stylus:clippypnpm run stylus:build
Required deployment environment secrets:
ARBITRUM_SEPOLIA_RPC_URLROBINHOOD_TESTNET_RPC_URLDEPLOYER_PRIVATE_KEYorETH_PKETHERSCAN_API_KEYorARBISCAN_API_KEYfor Arbitrum native verification
Broker and portal integrations should treat:
PolicyVaultas the provider commit surfacePaymentModuleas the settlement surfaceAccessReceiptas the portable proof surface
Do not integrate against the historical single-contract paywall model from earlier POC iterations.