Testnet, tokens have no value. Do not send real assets.
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