# Nexus CA: Creating and Submitting Intents (API)
This guide shows two ways to create and submit Nexus CA intents:
- Option A: Submit to the Statekeeper API
- Option B: Call the Settler contract directly on-chain
Examples are derived from a working integration flow.
## Prerequisites
- A funded (gas) test wallet on the source chain(s)
- RPC URL for each chain
- ERC-20 allowance for Settler to spend the source token
- Statekeeper service (for API path)
- Base URL: `STATEKEEPER_URL` (default `http://localhost:8080`)
- Endpoints: `/v1/supported-chains`, `/v1/quote`, `/v1/submit`
## Step 1: Build an Intent
Pick source/destination chains and tokens (with matching `nexus_token_id`). Create:
- A source batch with a Lock
- A destination batch with a Fund
- Inputs/outputs matching your transfer
- A strong random `nonce` (U256)
Rust outline:
```rust
// Pseudocode outline
let source_batch = Actions {
domain: source_domain.to_string(),
conditions: vec![],
funds: vec![],
settler: FixedBytes::from(source_chain.settler_contract),
locks: vec![Lock { token: FixedBytes::from(source_token), amount: U256::from(1_000_000) }],
actions: vec![],
};
let destination_batch = Actions {
domain: destination_domain.to_string(),
conditions: vec![],
funds: vec![Fund {
token: FixedBytes::from(destination_token),
amount: U256::from(1_000_000),
recipient: FixedBytes::from(padded_address_32),
}],
settler: FixedBytes::from(destination_chain.settler_contract),
locks: vec![],
actions: vec![],
};
let intent = Intent {
domain: source_domain.to_string(),
batches: vec![source_batch, destination_batch],
sender: FixedBytes::from(padded_address_32),
recipient: FixedBytes::from(padded_address_32),
nonce, // random U256
inputs: vec![Resource {
token: FixedBytes::from(source_token),
amount: U256::from(1_000_000),
recipient: FixedBytes::from(source_chain.settler_contract),
domain: source_domain.to_string(),
}],
outputs: vec![Resource {
token: FixedBytes::from(destination_token),
amount: U256::from(1_000_000),
recipient: FixedBytes::from(padded_address_32),
domain: destination_domain.to_string(),
}],
};
```
Tip: pad EVM addresses to 32 bytes (20 address bytes + 12 zero bytes).
## Step 2: Quote the Intent (Normalize + Fees)
Send your raw Intent to `/v1/quote`. Use the response Intent for execution.
Curl:
```bash
curl -X POST "$STATEKEEPER_URL/v1/quote" \
-H "Content-Type: application/json" \
-d '{
"domain": "...",
"batches": [ ... ],
"sender": "...",
"recipient": "...",
"nonce": "...",
"inputs": [ ... ],
"outputs": [ ... ]
}'
```
Rust:
```rust
let quote = reqwest::Client::new()
.post(format!("{}/v1/quote", statekeeper_url))
.json(&intent)
.send()
.await?
.json::<Intent>()
.await?;
```
Note: If needed, align `resource.amount` with the selected lock amount (as done in tests).
## Step 3A: Execute via API (/v1/submit)
Sign the quoted Intent hash and POST the `SignedIntent` to `/v1/submit`.
Curl (structure):
```bash
curl -X POST "$STATEKEEPER_URL/v1/submit" \
-H "Content-Type: application/json" \
-d '{
"intent": { ...quotedIntent... },
"signature": "0x..." // ECDSA signature bytes
}'
```
Rust (simplified):
```rust
let signed = SignedIntent {
intent: quote.clone(),
signature: Signature::from(real_signature_bytes),
};
let resp = reqwest::Client::new()
.post(format!("{}/v1/submit", statekeeper_url))
.json(&signed)
.send()
.await?;
```
Pros:
- Easiest path: Statekeeper coordinates and indexes on-chain activity
## Step 3B: Execute Directly On-Chain (Settler.fill)
Call the Settler contract with the ABI-encoded Intent.
1) Ensure allowance:
```rust
let allowance = erc20.allowance(signer.address(), settler_address).call().await?;
if allowance < U256::from(1_200_000) {
erc20.approve(settler_address, U256::from(10_000_000)).send().await?;
}
```
2) ABI-encode and send:
```rust
let origin_data = Intent::abi_encode("ed_intent);
let intent_id = quoted_intent.hash(); // [u8; 32]
let tx = INexusSettler::new(settler_address, provider.clone())
.fill(
intent_id.into(),
Bytes::from(origin_data),
Bytes::from(""), // signature if your contract requires
)
.send()
.await?;
println!("fill tx hash: {:?}", tx.tx_hash());
```
Pros:
- Full control of on-chain execution flow
- Works without posting to the API (Statekeeper can still index)
## Supported Chains
Discover chain details (settler address, tokens, domains):
Curl:
```bash
curl "$STATEKEEPER_URL/v1/supported-chains"
```
Rust:
```rust
let chains = reqwest::Client::new()
.get(format!("{}/v1/supported-chains", statekeeper_url))
.send()
.await?
.json::<Vec<ChainDetails>>()
.await?;
```
## Troubleshooting
- Revert: `ERC20: transfer amount exceeds balance`
- Ensure your wallet holds enough tokens for the lock amount.
- Allowance too low
- Call `approve(settler, amount)` and wait for confirmation before calling `fill`.
- Signature rejected on `/v1/submit`
- Sign the exact Intent hash in the expected format; verify signing scheme and encoding.
- RPC differences
- It’s fine to use your own RPCs; just ensure they match the chain.
## Minimal End-to-End (Quick Reference)
1) Build Intent (source -> destination)
2) Quote via `/v1/quote`
3) Adjust `resource.amount` if needed
4) Execute:
- API: `/v1/submit` with `SignedIntent`
- On-Chain: `Settler.fill(intentId, originData, signature)`