Skip to main content

Ripple policy for DALP intents

The properties DALP sets on every Ripple intent, how to write Ripple policies that match them, and the operation names that changed between DALP 3.1 and 3.2.

Ripple Custody is a custody provider. It decides who must approve each request that DALP files with it, in the custody approval step before it signs. Ripple calls that request an intent and decides with its policies. This page lists the properties DALP sets on every intent, so that a policy can match them. A deployment that upgrades from 3.1 also changes the policies that name an old operation, as Operation names that changed from 3.1 to 3.2 lists. A new 3.2 installation can skip that section.

A policy that matches nothing stops the intent. Ripple runs only the intents that a policy covers, so a policy that names an old value leaves the matching operations unable to run. Set your policies before you onboard investors, and test them with one write of each kind you use.

How Ripple picks a policy

Ripple runs one policy for each intent. Among the policies that apply to the intent and whose condition matches, Ripple runs the one with the highest rank, or the older one at equal rank. An intent that no policy matches cannot run.

Ripple runs the highest ranked policy that matches the properties of an intent.An intent from DALP carries a description and the properties dalpKind token.grant-role, dalpRoles admin and dalpChainId 11155111. Ripple checks three policies. The rank 40 policy on dalpRolesUndescribed does not match, because DALP did not set that property. The rank 30 policy on dalpRoles containing admin matches. The rank 10 policy on dalpKind token.grant-role also matches. Ripple runs the rank 30 policy, the highest rank that matches. An intent that matches no policy cannot run.Ripple runs the highest ranked policy that matches the properties of an intent.An intent from DALP carries a description and the properties dalpKind token.grant-role, dalpRoles admin and dalpChainId 11155111. Ripple checks three policies. The rank 40 policy on dalpRolesUndescribed does not match, because DALP did not set that property. The rank 30 policy on dalpRoles containing admin matches. The rank 10 policy on dalpKind token.grant-role also matches. Ripple runs the rank 30 policy, the highest rank that matches. An intent that matches no policy cannot run.

How Ripple picks one policy for an intent from its properties and the rank of each matching policy.

DALP signs each intent that it files, so Ripple treats it as a user-signed proposal. A policy that omits the intent origin matches it. A policy for system-signed proposals only does not. Under Ripple's CoerceDescendants governing strategy, a policy in a parent domain overrides a policy in a child domain, whatever its rank.

Ripple matches a value exactly with is, or matches text inside it with contains, and combines conditions with AND and OR, as its policy reference describes.

What DALP sets on every intent

Each intent carries a description for the approver and the custom properties below. The description starts with the name and symbol of the contract the request calls, such as Acme Bond (ACME): mint(...), when the request calls one contract that has a name. It names the action, the asset or contract, the amount, and the counterparty, and DALP keeps it to 250 characters. A call that also sends the chain's coin ends with that amount, such as depositTo(account=0x…) with 0.04 native. A list of addresses, numbers or true/false values shows its first three entries and how many more follow. A list of roles shows the role names, and any other list shows its count, such as [2 items]. A list that holds a number of more than 32 digits, such as a list of claim topic ids, shows its count. A country code passed as a number in its own argument shows as its two letters and its number, such as SA (682). A country code inside a structure does not read this way: a token created from an instrument template passes its settings as one structure, so its description shows the name, symbol, type and decimals of the token and not its country. A description that runs past 250 characters is cut from the end of the call, so a call that lists addresses can lose its last arguments, such as the country list of a batch registration. DALP limits each property value to 100 characters. DALP writes the description and every property itself, and Ripple does not check them against the request, so a policy that matches them trusts DALP's account of the request.

Property
What DALP sends
dalpKind
The DALP operation, for example token.mint or paymaster.sponsorship. The list under this table says which operation each kind of request sends.
dalpRoles
The roles that the request changes, separated by commas, for example supplyManagement,custodian. Each role is on the contract in dalpContract.
dalpRolesUndescribed
true when DALP cannot list every role the request changes. DALP leaves it out otherwise.
dalpContract
The address of the contract the request is for, in lowercase: the contract a transaction calls, or the contract that checks a signature. On a user operation, it appears only with dalpRoles, so a policy on dalpContract matches a user operation only when the operation changes roles.
dalpChainId
The network, as a decimal chain ID, for example 11155111.
dalpLane
order when Ripple signs and sends a transaction, manifest when Ripple signs a payload and returns the signature, account when Ripple creates or updates an account, display when Ripple files a ticker or a named endpoint.
dalpSignature
On a manifest, what Ripple signs: typed-data, message, userop for a user operation, or raw for a transaction that DALP sends.
dalpEip712Domain
On typed data and user operations, the name of the signing domain and its version, for example DALPIdentityFactory v3.
dalpEip712PrimaryType
On typed data and user operations, the type of the message that Ripple signs, for example CreateIdentityWithManagementKeys.
dalpEip712Verifier
On typed data and user operations, the contract that checks the signature, in lowercase.
dalpEip712Message<Field>
On typed data and user operations, one property for each field of the message that has a value, for example dalpEip712MessageWallet. An address is in lowercase and a number is in decimal. A list is separated by commas and cut like dalpRoles. A field that holds another structure is left out.
dalpOrganizationId
The DALP organisation that filed the intent.
dalpExternalId
DALP's ID for the request, to trace an intent back to DALP.

The operation in dalpKind depends on the request:

  • A data feed, settlement, token sale or trading venue transaction sends its own operation, for example feeds.submit, xvp.execute, token-sale.buy or tradingvenue.halt-market.
  • A custody withdrawal payout sends custody-ops.withdrawal-payout.
  • When a settlement or token sale write first sends an ERC20 allowance, that transaction carries the same kind as the write, for example token-sale.buy, and dalpContract names the token.
  • A transaction that names no operation sends txqueue.enqueue, the generic name of DALP's transaction queue.
  • A smart account runs its operations as user operations. When Ripple holds the owner key of a smart account, the user operation that Ripple signs sends the operation that it runs, for example token.mint, so a policy on dalpKind is token.mint matches that mint too. DALP sends userOp only for a user operation that carries no operation name.
  • On a multisig wallet, a smart account that needs signatures from more than one member, the signature of the member who starts the operation also sends the operation, and each approval sends user-operation.approve.
  • When Ripple holds the key that submits user operations to the chain, that transaction sends aa.bundle.
  • With named endpoints on, the request that files a ticker sends custody.display.ticker, and the request that files an endpoint sends custody.display.endpoint.

DALP 3.1 sent every property in this table except dalpRolesUndescribed and the dalpEip712 properties. The dalpKind values changed, as the last section lists.

Name contract destinations

RIPPLE_NAMED_ENDPOINTS, the config key signer.ripple.namedEndpoints, is off by default. While it is off, DALP makes no ticker, endpoint or ledger call, every order goes to the bare address it calls, and the intent description names the contract. The chart has no value for it. Set it in the signer.ripple config override that a Ripple signer needs.

With it on, DALP files a Ripple ticker for each token it creates and a named endpoint for each organisation smart wallet, system contract and token. An order to a contract goes to its endpoint once Ripple approves the endpoint and leaves it unlocked. Any other order keeps the bare address. The setting also sends text messages as EIP-191 content, so an approver reads the message instead of prefixed bytes.

A policy written for orders to an address can stop matching an order to an endpoint, and the same holds for a message of the EIP-191 type. Check both before you turn the setting on. Only tokens and contracts deployed after that get an endpoint, unless an administrator adds older ones by hand.

Policies for role changes

A role change is the request most worth a stricter workflow. DALP marks each role change so that a policy can find it.

When the role list is longer than 100 characters, DALP sends the leading role names that fit, adds +N for the N roles it left out, and sets dalpRolesUndescribed to true. A grant of twelve roles on the platform's access manager contract, for example, can send admin,auditor,systemManager,tokenManager,complianceManager,claimPolicyManager,claimIssuer,+5.

DALP also sets dalpRolesUndescribed to true, with no dalpRoles, when it cannot name every role, or when the roles are not all on one contract:

  • A role ID that DALP does not know.
  • A batch that changes roles and also calls another contract.
  • A call that passes a role change on to another contract to run.
  • A call that carries other calls inside it and that DALP cannot unpack, even when it changes no role.

DALP reads roles only from calls to functions that it knows. A call to a function that DALP does not know adds no name to dalpRoles and does not set dalpRolesUndescribed, even when it changes a role, so that intent gets the policy that its other properties match.

Add a dalpRolesUndescribed policy and rank it above your role policies, because Ripple runs only one policy for each intent.

  1. Add one policy that matches dalpRolesUndescribed is true. Apply it to every intent type, intent origin and domain that your role policies apply to, and give it your strictest workflow for role changes.
  2. Give it a higher rank than every policy that a role change can also match.
  3. Keep it in the domain of your role policies or in a parent domain, because of the CoerceDescendants rule above.

A request with dalpRolesUndescribed set then gets that workflow, never a looser one that matched only the names that fit. A role change through a function DALP does not know sets neither property, so this policy does not catch it.

Role changes reach Ripple as transaction orders and as manifests, so apply each role policy to both intent types. To match a single role, use contains, because is on dalpRoles matches only the complete list. Before you use contains, check that no longer role name includes the same text.

One request can change more than one role, and Ripple still runs only one policy. Rank your role policies by how strict their workflows are, with the strictest highest, and above any other policy that a role change can also match, such as a policy on dalpKind. A grant of auditor and admin together then gets the stricter admin workflow. Never give two role policies with different workflows the same rank, because at equal rank Ripple runs the older policy.

Keep the paymaster and bundler wallets approval-free

Two DALP wallets sign for every user, not for one investor. The paymaster wallet signs a sponsorship ticket, with dalpKind paymaster.sponsorship, for each operation that DALP pays gas for. The wallet that submits user operations to the chain sends aa.bundle. A policy that holds either intent for approval stops every sponsored operation on that network, not one.

Give these two intents a policy whose workflow needs no approver, and rank it ahead of every approval policy that they also match. When a policy holds a sponsorship ticket, DALP refuses the operation with “The paymaster wallet's Ripple policy holds sponsorship ticket signing”, and names the Ripple intent. For each hold on either wallet, DALP logs one warning and adds one to the dalp_execution_infrastructure_vault_holds_total counter.

Operation names that changed from 3.1 to 3.2

A policy whose condition names a 3.1 value stops matching after the upgrade. Its intents then go to the next policy that matches, or cannot run when no policy matches. Until every deployment you run is on 3.2, match both values with an OR condition, so that an intent from either version finds its policy.

DALP 3.1 sent contract. and the contract function name when a workflow wrote to a contract directly, for example during organisation setup, invitations, token creation and system migration. Writes that came through the API sent the operation name of their route, and many of those names changed too. 3.2 sends an operation name for both. The tables pair each 3.1 value with its 3.2 name. The values token.mint, token.burn, token.pause, token.unpause and xvp.execute kept their 3.1 names. For any other value that the tables do not list, run the operation on a 3.2 test deployment and match the value that Ripple shows.

Direct contract writes

3.1 value
3.2 value
contract.createSystem
system.create-system
contract.bootstrap
system.bootstrap
contract.grantRole, contract.batchGrantRole, contract.grantMultipleRoles on the system access manager
system.grant-role
contract.revokeRole, contract.batchRevokeRole, contract.revokeMultipleRoles on the system access manager
system.revoke-role
The same three grant values on a token's access manager
token.grant-role
contract.registerTopicScheme, contract.updateTopicScheme, contract.removeTopicScheme
system.topic-create, system.topic-update, system.topic-delete
contract.addTrustedIssuer, contract.updateIssuerClaimTopics, contract.removeTrustedIssuer
system.trusted-issuer-create, system.trusted-issuer-update, system.trusted-issuer-delete
contract.installScopedModule, contract.uninstallModule on the system compliance module
system.compliance-module-configure
contract.registerComplianceModule
system.compliance-module-create
contract.registerSystemAddon
system.addon-factory-create
contract.setAddonImplementation, contract.updateImplementation on an add-on
system.addon-set-implementation, system.addon-factory-update-implementation
contract.registerIdentity, contract.deleteIdentity
system.identity-register, system.identity-delete
contract.registerPendingIdentity
system.identity-register-pending
contract.registerClaimAuthorizationContract
system.identity-register-claim-authorization
contract.createIdentityWithManagementKeyAuthorization
system.identity-create.create
contract.addKey when an identity is created
system.identity-create.mgmt-key- and the wallet address in lowercase
contract.addKey when an organisation key is added
system.identity-add-key
contract.addKey for a claim issuer key
system.identity-add-management-key or system.identity-add-action-key
contract.upgradeToAndCall, contract.migrate
system.upgrade-proxy, system.migrate
contract.setImplementation on the system or its compliance module registry
system.set-implementation, system.compliance-module-set-implementation
contract.registerTokenFactory
system.factory-create
contract.setTokenFactoryImplementation, contract.updateTokenImplementation
system.factory-set-implementation, system.factory-update-token-implementation
contract.remapKey on the token factory registry
system.factory-remap-key
contract.registerFeed
feeds.register
contract.batchCreate for exchange rate feeds
feeds.exchange-rate.create

Token operations

3.1 value
3.2 value
token.grantRole, token.revokeRole
token.grant-role, token.revoke-role
token.installComplianceModule, token.uninstallComplianceModule
token.install-compliance-module, token.uninstall-compliance-module
token.installScopedComplianceModule
token.install-scoped-compliance-module
token.configureComplianceModule, token.configureScopedComplianceModule
token.configure-compliance-module, token.configure-scoped-compliance-module
token.setModuleScope
token.set-module-scope
token.setMetadata, token.removeMetadata
token.set-metadata, token.remove-metadata
token.setYieldSchedule
token.set-yield-schedule
token.freezePartialTokens, token.unfreezePartialTokens
token.freeze-partial, token.unfreeze-partial
token.recoverTokens, token.recoverERC20, token.forcedRecoverTokens
token.recover-tokens, token.recover-erc20, token.forced-recover
token.forcedTransfer from the API
token.forced-transfer. A batch of forced transfers keeps token.forcedTransfer.
token.transfer-approval.approve, token.transfer-approval.revoke
token.transfer-approval-approve, token.transfer-approval-revoke
token.convertYield
token.convert-yield: and the batch
token.feature and the feature name, then Create, for example token.featureAumFeeCreate
token.feature-create
token.feature and the feature name, then Attach, for example token.featureAumFeeAttach
token.feature-attach. When the feature contract already exists, token.feature.attach.
token.featureDetach, token.featurePermitDetach
token.feature-detach
token.featureRotateCreate, token.featureRotateAttach
token.feature-rotate-create, token.feature-rotate-attach
addon.fixedYieldSchedule.create, .topUp, .withdraw, .claim
features.fixed-yield.create, features.fixed-yield.top-up, features.fixed-yield.withdraw, features.fixed-yield.claim
token.create.role, the role grant that token creation files
token.grant-role
claim.issue and claim.revoke from the API
system.claim-issue or token.claim-issue, and system.claim-revoke or token.claim-revoke

The pairs above are the values the API sends when you attach, replace or detach a feature on an existing token. A token that you create with its features, and a feature that a creation flow replaces, are filed under token.feature. and the feature ID, then .create or .attach. A replacement in a creation flow keeps token.featureRotateCreate for the create step and sends token.feature-rotate-attach for the attach step.

System, identity and wallet operations

3.1 value
3.2 value
system.factory.create
system.factory-create
system.grant-role-by-participant, system.revoke-role-by-participant
system.grant-role, system.revoke-role
compliance.provider-webhook-reactivate
system.trusted-issuer-add-claim-topic
compliance.provider-webhook-revoke
system.trusted-issuer-remove-claim-topic
system.identity-create.mgmt-key
system.identity-create.mgmt-key- and the wallet address in lowercase
system.identity-update-country: and the wallet
system.identity-update-country
identity-recovery.identity-create
system.identity-create.create
identity-recovery.recover-identity
identity.recover
invitation.identity-attach.register-pending
system.identity-register
invitation.identity-attach.smart-wallet-mgmt-key
system.identity-add-management-key
invitation.identity-attach.register-claim-auth
system.identity-register-claim-authorization
claim.issue.add-claim
identity.claim-add
smart-wallet.create, smart-wallet.create.multisig, when they named the account creation order
custody.smart-wallet-create, custody.smart-wallet-create.multisig
smart-wallet.identity.add-key
custody.smart-wallet-create.add-key
smart-wallet.authorise, identity-recovery.authorise
smart-wallet.create. 3.1 used this string for the account creation order, so a policy on smart-wallet.create that you wrote for 3.1 now matches a different intent.
system.identity-create.register-pending, system.identity-create.register-controller
system.identity-register-pending.wallet: and the wallet address in lowercase
system.identity-create.batch-register-pending
system.identity-register-pending.batch: and the wallet addresses in lowercase, joined with hyphens
system.identity-create.claim-auth
system.identity-create.claim-auth when an identity is created, and system.identity-register-pending.claim-auth when a pending identity is registered. A policy on the 3.1 value no longer matches the second.
identity-recovery.register-replacements
identity.register for a wallet with no identity, and identity.recover for the others
No value, for a user operation that a smart account runs
The operation that it runs, for example token.mint. A policy on token.mint now also matches that operation when a smart account runs it.
smart-wallet.install-module, smart-wallet.uninstall-module, smart-wallet.set-threshold
custody.wallet-mutation
sponsorship.ticket
paymaster.sponsorship
paymaster.set-signer. and the paymaster address
paymaster.set-signer
refundSplitter.recoverNative
system.refund-splitter-recover-native
xvp.approve.erc20. or xvp.approve.settlement. and the addresses
xvp.approve
feeds.feed-submit, feeds.issuer-signed-create
feeds.submit, feeds.issuer-signed.create

Operations that 3.2 no longer sends

These 3.1 values have no 3.2 successor. Remove a policy condition that names only one of them.

  • paymaster.set-signer-rollback. and the paymaster address
  • refundSplitter.recoverErc20, refundSplitter.setSponsorship
  • token.delegateBySig
  • token-sale.add-payment-currency, token-sale.remove-payment-currency, token-sale.configure-vesting, token-sale.set-purchase-limits, token-sale.set-soft-cap
  • system.identity-delete.paired. and the wallet, replaced by system.identity-delete

Check your policies before you upgrade

  1. Run the operations that your policies cover on a 3.2 test deployment.
  2. Compare the dalpKind that Ripple shows for each intent with the condition of the policy you expect to run.
  3. Check each policy against the property table above, and add the dalpRolesUndescribed policy.

Troubleshooting

What you see
Cause
Fix
An intent cannot run.
No policy matches the intent, often because a condition still names a 3.1 dalpKind.
Match the 3.2 value, or both values with OR. Then submit the operation again.
A role grant gets a looser workflow than you set.
A policy on dalpKind or dalpRoles has a higher rank than the dalpRolesUndescribed policy.
Rank the dalpRolesUndescribed policy ahead of every policy that a role change can match.
A sponsored operation fails with “The paymaster wallet's Ripple policy holds sponsorship ticket signing”.
A policy with an approval workflow matches paymaster.sponsorship intents.
Rank a policy without approvers for that intent ahead of it, then submit the operation again.
A request stays in signing, and its error message says that the account has an amount in quarantine.
Ripple quarantines funds that it receives from an outside address, a minted token included, and an order waits until its funds are released. The message names each quarantined token or gas amount.
Release the quarantine in the Ripple console. DALP never releases a quarantine.
An intent stays open in Ripple after DALP stops waiting, and the Console shows the request as “May still go through”.
Ripple has no call that withdraws a held intent, so DALP does not cancel one. DALP stops waiting after its poll limit and reports the intent as open.
Let the intent run or stop it in Ripple. Do not submit the operation again while the intent is open.
A request stays in signing, and its error message says that Ripple has not prepared or broadcast an order.
Ripple holds the order. After ten minutes without a broadcast, DALP names the order on the request, says how long ago it filed the order, and keeps waiting.
Find the order by its id in Ripple, and let it run or stop it there.

Ripple sources

  • Policy reference: the conditions a policy can use, and how Ripple ranks and picks a policy.

Was this page helpful?