Appendix A: Example End-to-End Intent Payload
The serialized ExecutionIntent payload sent by the Relayer to the public vault contract contains all the verifiable instructions required for execution without exposing sensitive policy details.
Intent Payload Structure
JSON Format
json
{
"vaultId": "vault-eth-001",
"nonce": 42,
"actions": [
{
"protocolId": "0x4161766500000000000000000000000000000000000000000000000000000000",
"protocol": "Aave",
"operation": "withdraw",
"params": {
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": "50000000000",
"recipient": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
}
},
{
"protocolId": "0x556e697377617056330000000000000000000000000000000000000000000000",
"protocol": "UniswapV3",
"operation": "swap",
"params": {
"tokenIn": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"tokenOut": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"amountIn": "50000000000",
"minAmountOut": "16500000000000000000",
"fee": "3000",
"recipient": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
}
}
],
"deadline": "2025-10-14T12:00:00Z",
"issuerPartyId": "CantonIssuer::0x1A2B3C4D5E6F..."
}ABI-Encoded Payload
Solidity Struct Definition
solidity
struct ExecutionIntent {
string vaultId;
uint256 nonce;
Action[] actions;
uint256 deadline;
}
struct Action {
bytes32 protocolId;
bytes4 selector;
bytes params;
}Encoding Example
typescript
import { ethers } from 'ethers';
function encodeIntent(intent: Intent): string {
const intentType = [
'tuple(string vaultId, uint256 nonce, tuple(bytes32 protocolId, bytes4 selector, bytes params)[] actions, uint256 deadline)'
];
const encoded = ethers.utils.defaultAbiCoder.encode(
intentType,
[{
vaultId: intent.vaultId,
nonce: intent.nonce,
actions: intent.actions.map(a => ({
protocolId: ethers.utils.id(a.protocol),
selector: ethers.utils.id(a.operation).slice(0, 10),
params: encodeActionParams(a)
})),
deadline: Math.floor(new Date(intent.deadline).getTime() / 1000)
}]
);
return encoded;
}
function encodeActionParams(action: Action): string {
switch (action.operation) {
case 'withdraw':
return ethers.utils.defaultAbiCoder.encode(
['address', 'uint256', 'address'],
[action.params.asset, action.params.amount, action.params.recipient]
);
case 'swap':
return ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint24', 'address'],
[
action.params.tokenIn,
action.params.tokenOut,
action.params.amountIn,
action.params.minAmountOut,
action.params.fee,
action.params.recipient
]
);
default:
throw new Error(`Unknown operation: ${action.operation}`);
}
}Complete Transaction Flow
1. Canton Generates Intent
daml
-- In Canton
choice GenerateIntent : ContractId ExecutionIntent
controller operator
do
now <- getTime
create ExecutionIntent with
vaultId = "vault-eth-001"
nonce = 42
actions = [
Action with
protocol = "Aave"
operation = "withdraw"
params = [("asset", "0xA0b8..."), ("amount", "50000000000")]
,
Action with
protocol = "UniswapV3"
operation = "swap"
params = [
("tokenIn", "0xA0b8..."),
("tokenOut", "0xC02a..."),
("amountIn", "50000000000"),
("minAmountOut", "16500000000000000000")
]
]
deadline = addRelTime now (hours 1)
issuer = operator2. Canton Signs Intent
typescript
// Canton signing service
const payload = encodeIntent(intent);
const payloadHash = ethers.utils.keccak256(payload);
// Sign with Canton's HSM-backed key
const signature = await cantonSigner.signMessage(
ethers.utils.arrayify(payloadHash)
);
console.log('Payload Hash:', payloadHash);
// 0x7b3f8e9a2c1d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
console.log('Signature:', signature);
// 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b...3. Relayer Submits to Chain
typescript
// Relayer prepares transaction
const vaultContract = new ethers.Contract(
VAULT_ADDRESS,
VAULT_ABI,
relayerWallet
);
const tx = await vaultContract.executeIntent(
payload, // ABI-encoded intent
signature, // Canton's signature
'0x' // Empty proof for Pattern 1
);
console.log('Transaction submitted:', tx.hash);
// 0x9f8e7d6c5b4a3928170615e4d3c2b1a0998877665544332211
await tx.wait();
console.log('Transaction confirmed');4. Vault Verifies and Executes
solidity
// In the vault contract
function executeIntent(
bytes calldata intentPayload,
bytes calldata cantonSig,
bytes calldata proof
) external nonReentrant whenNotPaused {
// 1. Verify Canton signature
bytes32 payloadHash = keccak256(intentPayload);
address signer = recoverSigner(payloadHash, cantonSig);
require(signer == cantonSigner, "Invalid signature");
// 2. Decode intent
ExecutionIntent memory intent = abi.decode(
intentPayload,
(ExecutionIntent)
);
// 3. Validate
require(intent.nonce > nonce, "Invalid nonce");
require(block.timestamp <= intent.deadline, "Expired");
// 4. Execute actions
for (uint i = 0; i < intent.actions.length; i++) {
Action memory action = intent.actions[i];
address adapter = adapters[action.protocolId];
(bool success, ) = adapter.call(
abi.encodePacked(action.selector, action.params)
);
require(success, "Action failed");
}
// 5. Update nonce
nonce = intent.nonce;
emit IntentExecuted(intent.nonce, payloadHash);
}Privacy Analysis
Data Exposed Publicly
| Field | Value | Visibility |
|---|---|---|
| Vault ID | vault-eth-001 | Public (on-chain) |
| Nonce | 42 | Public (on-chain) |
| Protocol | Aave, UniswapV3 | Public (on-chain) |
| Operation | withdraw, swap | Public (on-chain) |
| Token Addresses | 0xA0b8..., 0xC02a... | Public (on-chain) |
| Amounts | 50000000000, etc. | Public (on-chain) |
| Deadline | 2025-10-14T12:00:00Z | Public (on-chain) |
Data Kept Private (Canton Only)
| Field | Description | Visibility |
|---|---|---|
| Strategy Algorithm | Optimization logic | Private (Canton) |
| Risk Scores | Internal risk assessment | Private (Canton) |
| Oracle Data | Proprietary price feeds | Private (Canton) |
| Policy Details | Full policy constraints | Private (Canton) |
| Approval Discussion | Internal deliberations | Private (Canton) |
| Future Plans | Upcoming strategy changes | Private (Canton) |
Complete End-to-End Example
1. Canton: Strategy computes optimal allocation
Private: "Move 30% from Aave to Uniswap based on risk score 7.2"
2. Canton: Policy check passes
Private: maxExposure=50%, current=30%, new=40% ✓
3. Canton: Generate and sign intent
Public Payload: {
vaultId: "vault-eth-001",
nonce: 42,
actions: [withdraw from Aave, swap on Uniswap],
deadline: "..."
}
Signature: 0x1a2b3c...
4. Relayer: Verify signature and submit
Transaction: 0x9f8e7d...
5. Vault Contract: Verify and execute
Event: IntentExecuted(nonce=42, hash=0x7b3f...)
6. Relayer: Report back to Canton
Private: success=true, gasUsed=187423, slippage=0.12%
7. Canton: Update internal state
Private: Portfolio now 40% Uniswap, performance +2.3%Conclusion
This JSON payload is cryptographically signed by the Canton Policy Issuer Party, resulting in the cantonSig. The Relayer then packages this signed payload and submits it, along with any necessary accompanying signatures or proofs, to the public vault contract for execution.
The payload contains the minimum necessary data for execution while keeping all sensitive strategy details, risk assessments, and policy logic completely private within the Canton domain.
