Skip to Content
GuidesRestrict who may hold

Restrict who may hold

Create an asset only accounts attested investor.verified by the issuer may hold, with at most two holders; watch a transfer to an unattested account be refused, and read the rule that refused it.

1. Create the asset with a combined policy

POLICY="{\"all\":{\"rules\":[{\"credential\":{\"type\":\"investor.verified\",\"attesters\":[\"$ISSUER\"]}},{\"max_holders\":{\"n\":2}}]}}" R=$(alias_tx asset create-asset ECONOMIC --settlement-denom aeusd --policy "$POLICY" --from issuer) RASSET=$(wait_tx "$(jq -r .txhash <<<"$R")" | created_asset) echo "restricted asset: $AEVA_EXPLORER/assets/$RASSET?tab=kinds"

The issuer must pass the policy too, so it attests itself first, then issues:

R=$(alias_tx credential attest "$ISSUER" investor.verified +720h --from issuer); wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null R=$(alias_tx rights issue "$RASSET" 0 "$ISSUER" 100 --from issuer); wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null aevad query rights holder-count "$RASSET" 0 --node "$AEVA_NODE" -o json | jq -c .

2. A transfer to an unattested account is refused

aevad keys add stranger --home "$AEVA_HOME" --keyring-backend test --no-backup >/dev/null 2>&1 || true STRANGER=$(aevad keys show stranger -a --home "$AEVA_HOME" --keyring-backend test) R=$(alias_tx rights transfer "$STRANGER" "$RASSET" 0:10 --from issuer) wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null || true echo "refused tx: $AEVA_EXPLORER/tx/$(jq -r .txhash <<<"$R")"
Expected output
tx refused: code 3 (policy): failed to execute message; message index: 0: policy denied at all[0].credential(investor.verified): … refused tx: https://explorer.aevachain.com/tx/…

wait_tx prints the refusal. The transaction is in a block — it paid its fee — but failed; nothing moved. The rule path all[0].credential(investor.verified) names the first rule of the ALL that refused.

3. Attest the investor; the transfer goes through

R=$(alias_tx credential attest "$INVESTOR" investor.verified +720h --from issuer); wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null R=$(alias_tx rights transfer "$INVESTOR" "$RASSET" 0:10 --from issuer); wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null echo "transferred: $AEVA_EXPLORER/tx/$(jq -r .txhash <<<"$R")"

4. The holder cap

The asset now has two holders (issuer and investor). Attest the stranger and try again — the credential passes, the cap refuses:

R=$(alias_tx credential attest "$STRANGER" investor.verified +720h --from issuer); wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null R=$(alias_tx rights transfer "$STRANGER" "$RASSET" 0:10 --from issuer) wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null || true
Expected output
tx refused: code 3 (policy): failed to execute message; message index: 0: policy denied at all[1].max_holders(2): …

5. Revoke

Revoking a credential stops new transfers to that account; units already held stay where they are.

R=$(alias_tx credential revoke "$INVESTOR" investor.verified --from issuer); wait_tx "$(jq -r .txhash <<<"$R")" >/dev/null aevad query credential valid "$INVESTOR" investor.verified "$ISSUER" --node "$AEVA_NODE" -o json | jq -r '"investor credential valid: \(.valid // false)"' aevad query rights position "$RASSET" 0 "$INVESTOR" --node "$AEVA_NODE" -o json | jq -r '"investor still holds \(.position.units)"'
Expected output
investor credential valid: false investor still holds 10

See Transfers & policies and Credentials.

Last updated