# 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(&quoted_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)`