# API Reference Source: https://spiceflow-docs.spicenet.io/api-reference Complete API documentation for cross-chain transaction submission and intent execution ## Authentication The API uses signature-based authentication. For EIP-7702 mode, you sign authorizations with your wallet. For non-7702 modes, you sign or submit transactions directly. ## Response Format All API responses follow this format: ```json theme={null} { "success": boolean, "data": object | null, "error": { "message": string } | null } ``` ## Error Handling | Status | Description | | - | - | | `200/201` | Success | | `202` | Accepted (step already processing) | | `400` | Bad Request (validation error) | | `404` | Not Found | | `500` | Internal Server Error | *** ## Endpoints Overview ### Actions API The unified API for creating and executing cross-chain intents. | Method | Endpoint | Description | | - | - | - | | `POST` | `/actions` | [Create action with intents](/api-reference/endpoint/actions-create) | | `GET` | `/actions/:actionId` | [Get action status](/api-reference/endpoint/actions-get) | | `GET` | `/actions/:actionId/intents/:idx` | [Get intent status](/api-reference/endpoint/actions-get) | | `GET/POST` | `/actions/:actionId/intents/:idx/steps/:idx` | [Get/Execute step](/api-reference/endpoint/actions-execute) | > **[POST /actions](/api-reference/endpoint/actions-create)** > > Create an action with intents for cross-chain execution > **[GET Action Status](/api-reference/endpoint/actions-get)** > > Retrieve action, intent, or step status > **[POST Execute Step](/api-reference/endpoint/actions-execute)** > > Execute a specific intent step *** ### Balances & Deposits Manage user balances for cross-chain transfers. | Method | Endpoint | Description | | - | - | - | | `GET` | `/wallets/:address/balance` | [Get user Spice balances](/api-reference/endpoint/spice-deposit) | > **[Spice Deposits](/api-reference/endpoint/spice-deposit)** > > Record deposits, withdrawals, and query balances *** ### Transaction Tracking Register an on-chain transaction for the relayer to process. | Method | Endpoint | Description | | - | - | - | | `POST` | `/transactions/handle/evm` | [Handle a transaction](/api-reference/endpoint/venue-tx-hashes) | > **[Handle Transaction](/api-reference/endpoint/venue-tx-hashes)** > > Register an on-chain transaction hash for processing *** ### Utility | Method | Endpoint | Description | | - | - | - | | `POST` | `/airdrop/:chainId/:tokenId` | [Request airdrop](/api-reference/endpoint/airdrop) | | `GET` | `/health` | [Health check](/api-reference/endpoint/health) | > **[Airdrop](/api-reference/endpoint/airdrop)** > > Request testnet token airdrops > **[Health Check](/api-reference/endpoint/health)** > > Check API server status *** ### Legacy Endpoints These endpoints are deprecated and not served by the current relayer. Use the Actions API above. | Method | Endpoint | Description | | - | - | - | | `POST` | `/transaction/submit` | [Submit legacy transaction](/api-reference/endpoint/transaction-submit) | | `GET` | `/intent/:id/step/:id/status` | [Get step status](/api-reference/endpoint/intent-step-status) | > **[POST /transaction/submit](/api-reference/endpoint/transaction-submit)** > > Submit a cross-chain transaction (legacy) > **[GET Step Status](/api-reference/endpoint/intent-step-status)** > > Get intent step status (legacy) # POST /actions Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/actions-create Create a new action with one or more intents for cross-chain execution ## Endpoint ```http theme={null} POST /actions ``` ## Description This is the **unified entry point** for creating cross-chain intents. An action groups related intents together and provides a single tracking ID. Each intent within an action can have its own execution mode and chain batches. ## Request Body ```typescript theme={null} { user: Address; // User wallet address chainAuthorizations?: ChainAuth7702[]; // Required for 7702 mode intents: Intent[]; // Array of intents (min 1) } ``` ### Execution Modes > [!WARNING] > > `non-7702:presign` mode is **not yet implemented**. Only `7702` and `non-7702:on-demand` modes are currently functional. **7702** EIP-7702 mode uses delegate contract for execution. ```typescript theme={null} { mode: "7702", signatureType: string, signature: Hex, nbf: number, // Not before timestamp exp: number, // Expiration timestamp chainBatches: [{ hash: Hex, chainId: number, tokenTransfers: TokenTransfer[], calls: Call[] }] } ``` **non-7702:presign (Not Implemented)** > [!WARNING] > > This mode exists in schema but execution is not yet implemented. Presigned transactions for execution by the solver. ```typescript theme={null} { mode: "non-7702:presign", chainBatches: [{ chainId: number, tokenTransfers: TokenTransfer[], call: { to: Address, value: bigint, data: Hex, signature: Hex // User's presigned tx signature } }] } ``` **non-7702:on-demand** User sends transactions themselves, registers hash for solver fulfillment. ```typescript theme={null} { mode: "non-7702:on-demand", chainBatches: [{ chainId: number, tokenTransfers: TokenTransfer[], call: { to: Address, value: bigint, data: Hex } }] } ``` ### Schema Details #### TokenTransfer | Field | Type | Description | | - | - | - | | `from` | `Address \| "solver"` | Sender address or "solver" for solver-funded | | `to` | `Address` | Recipient address | | `token` | `Address` | Token contract address | | `amount` | `bigint` | Amount in wei | #### ChainAuthorization (7702 only) | Field | Type | Required | Description | | - | - | - | - | | `address` | `Address` | Yes | Address being authorized | | `chainId` | `number` | Yes | Chain ID | | `nonce` | `number` | Yes | Authorization nonce | | `r` | `Hex` | Yes | ECDSA signature R value | | `s` | `Hex` | Yes | ECDSA signature S value | | `yParity` | `number` | No | Y parity (default: 0) | ## Response ### Success Response (201) ```json theme={null} { "actionId": "act_a1b2c3d4e5f6...", "intentIds": [ "act_a1b2c3d4e5f6.../0", "act_a1b2c3d4e5f6.../1" ] } ``` | Field | Type | Description | | - | - | - | | `actionId` | `string` | Unique action identifier | | `intentIds` | `string[]` | Array of intent IDs (format: `{actionId}/{index}`) | ### Error Responses > **400 Bad Request** > > ```json theme={null} > { > "error": "schema validation failed", > "issues": { /* Zod validation errors */ } > } > ``` > > ```json theme={null} > { > "error": "chainAuthorizations required for 7702 intents" > } > ``` ## Example Request ```bash theme={null} curl -X POST /actions \ -H "Content-Type: application/json" \ -d '{ "user": "0x742d35Cc6634C0532925a3b844e4B7db0D6d8E5c", "chainAuthorizations": [{ "address": "0x742d35Cc6634C0532925a3b844e4B7db0D6d8E5c", "chainId": 84532, "nonce": 1, "r": "0x...", "s": "0x...", "yParity": 0 }], "intents": [{ "mode": "7702", "signatureType": "eip712", "signature": "0x...", "nbf": 0, "exp": 1999999999, "chainBatches": [{ "hash": "0x...", "chainId": 84532, "tokenTransfers": [{ "from": "0x742d35Cc6634C0532925a3b844e4B7db0D6d8E5c", "to": "0xVenueAddress...", "token": "0xTokenAddress...", "amount": "1000000000000000000" }], "calls": [{ "to": "0xTokenAddress...", "value": "0", "data": "0xa9059cbb..." }] }] }] }' ``` ## Workflow 1. **Create Action**: Submit action with intents 2. **Track Progress**: Use returned `actionId` and `intentIds` to monitor status 3. **Execute Steps**: Call `POST /actions/:actionId/intents/:intentIndex/steps/:stepIndex` for each step ## Next Steps After creating an action: * [Get Action Status](/api-reference/endpoint/actions-get) - Monitor action and intent status * [Execute Step](/api-reference/endpoint/actions-execute) - Execute individual steps # POST Execute Step Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/actions-execute Execute a specific intent step based on execution mode ## Endpoint ```http theme={null} POST /actions/:actionId/intents/:intentIndex/steps/:stepIndex ``` ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `actionId` | `string` | Yes | Action ID | | `intentIndex` | `number` | Yes | Intent index (0-based) | | `stepIndex` | `number` | Yes | Step index (0-based) | > [!WARNING] > > `non-7702:presign` mode is **not yet implemented**. Only `7702` and `non-7702:on-demand` modes are currently functional. ## Request Body by Mode **7702** For EIP-7702 intents, simply trigger execution: ```json theme={null} { "action": "execute" } ``` The solver executes the delegated calls using the stored authorization. **non-7702:presign (Not Implemented)** > [!WARNING] > > This mode exists in schema but execution is not yet implemented. For presigned transactions: **Register hash (store for later):** ```json theme={null} { "action": "register", "hash": "0xTransactionHash..." } ``` **Execute (broadcast stored tx):** ```json theme={null} { "action": "execute" } ``` **non-7702:on-demand** User sends their own transaction, then notifies the API: **Register (store hash only, for logging):** ```json theme={null} { "action": "register", "hash": "0xUserSubmittedTxHash..." } ``` **Execute (verify tx + update balances + trigger solver):** ```json theme={null} { "action": "execute", "hash": "0xUserSubmittedTxHash..." } ``` ## Response ### Success Response (200) ```json theme={null} { "status": "success", "transactionHash": "0x..." } ``` | Field | Type | Description | | - | - | - | | `status` | `string` | Step status after execution | | `transactionHash` | `string` | Transaction hash (if applicable) | ### Already Processed (202) If the step is not in `created` status: ```json theme={null} { "status": "executing" } ``` ## Execution Flow ### 7702 Mode 1. Solver retrieves stored calls and authorization 2. Broadcasts transaction with delegated execution 3. Updates step status on confirmation ### non-7702:on-demand Mode When `action: "execute"` with a transaction hash: 1. **Verify Transaction**: Check tx exists on-chain and succeeded 2. **Replay Prevention**: Reject if hash already processed 3. **Update Balances**: Credit user's Spice balance from token transfers 4. **Execute Solver Transfers**: Send tokens on destination chain(s) 5. **Auto-Advance**: Automatically execute remaining steps (solver transfers) When `action: "register"`: 1. **Verify Transaction**: Check tx exists and succeeded 2. **Store Reference**: Save hash for tracking, no balance updates 3. **Status**: Set to `registered` instead of `success` ## Error Responses ### 400 Bad Request ```json theme={null} { "error": "schema validation failed", "issues": { /* Validation details */ } } ``` ```json theme={null} { "error": "Transaction already processed" } ``` ```json theme={null} { "error": "Transaction failed or pending" } ``` ### 404 Not Found ```json theme={null} { "error": "could not find intent", "actionId": "act_...", "intentIndex": 0, "stepIndex": 0 } ``` ### 500 Internal Server Error ```json theme={null} { "error": "error executing intent step", "actionId": "act_...", "intentIndex": 0, "stepIndex": 0, "message": "Error details..." } ``` ## Example Requests ### 7702 Mode ```bash theme={null} curl -X POST /actions/act_abc123/intents/0/steps/0 \ -H "Content-Type: application/json" \ -d '{"action": "execute"}' ``` ### non-7702:on-demand Mode ```bash theme={null} # User sends their own transaction first, then: curl -X POST /actions/act_abc123/intents/0/steps/0 \ -H "Content-Type: application/json" \ -d '{ "action": "execute", "hash": "0x1234567890abcdef..." }' ``` ## Notes * Steps must be executed in order (0, 1, 2, ...) * For `non-7702:on-demand`, the user is responsible for sending the source chain transaction * After a successful `execute` action, remaining steps (solver transfers on destination chains) are auto-executed # GET Action & Intent Status Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/actions-get Retrieve action, intent, or step status for tracking execution progress ## Endpoints | Method | Endpoint | Description | | - | - | - | | `GET` | `/actions/:actionId` | Get full action with all intents | | `GET` | `/actions/:actionId/intents/:intentIndex` | Get specific intent | | `GET` | `/actions/:actionId/intents/:intentIndex/steps/:stepIndex` | Get specific step | *** ## GET /actions/:actionId Retrieve a complete action including all intents and their chain authorization steps. ### Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `actionId` | `string` | Yes | Action ID (e.g., `act_abc123...`) | ### Response (200) ```json theme={null} { "id": "act_abc123...", "user": "0x742d35Cc6634C0532925a3b844e4B7db0D6d8E5c", "status": "created", "intents": [ { "id": "act_abc123.../0", "mode": "7702", "status": "created", "executionIndex": 0, "chainAuthorizations": [ { "index": 0, "chainId": "84532", "status": "created", "mode": "7702" } ] } ] } ``` *** ## GET /actions/:actionId/intents/:intentIndex Retrieve a specific intent within an action. ### Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `actionId` | `string` | Yes | Action ID | | `intentIndex` | `number` | Yes | Intent index (0-based) | ### Response (200) ```json theme={null} { "id": "act_abc123.../0", "actionId": "act_abc123...", "mode": "7702", "status": "created", "executionIndex": 0, "signatureType": "eip712", "signature": "0x...", "nbf": "0", "exp": "1999999999", "chainAuthorizations": [ { "index": 0, "chainId": "84532", "status": "success", "txid": "0x...", "hash": "0x..." } ] } ``` *** ## GET /actions/:actionId/intents/:intentIndex/steps/:stepIndex Retrieve status of a specific chain authorization step. ### Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `actionId` | `string` | Yes | Action ID | | `intentIndex` | `number` | Yes | Intent index (0-based) | | `stepIndex` | `number` | Yes | Step index (0-based) | ### Response (200) ```json theme={null} { "intentId": "act_abc123.../0", "index": 0, "chainId": "84532", "mode": "7702", "status": "success", "hash": "0x...", "txid": "0xTransactionHash...", "txSentAt": "2024-01-15T10:30:00.000Z", "txConfirmedAt": "2024-01-15T10:30:15.000Z" } ``` ### Step Status Values | Status | Description | | - | - | | `created` | Step created, awaiting execution | | `registered` | Hash registered (non-7702:on-demand only) | | `executing` | Transaction sent, awaiting confirmation | | `success` | Step completed successfully | | `reverted` | Transaction reverted on-chain | | `error` | Execution error occurred | *** ## Error Responses ### 400 Bad Request ```json theme={null} { "error": "invalid params", "issues": { /* Validation errors */ } } ``` ### 404 Not Found ```json theme={null} { "error": "could not find action", "actionId": "act_invalid..." } ``` ```json theme={null} { "error": "could not find intent", "actionId": "act_abc123...", "intentIndex": 5 } ``` ```json theme={null} { "error": "could not find step", "actionId": "act_abc123...", "intentIndex": 0, "stepIndex": 10 } ``` # Airdrop Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/airdrop Request testnet token airdrops ## Endpoint ```http theme={null} POST /airdrop ``` Request one or more testnet token airdrops in a single call. Testnet only. ## Request Body ```json theme={null} { "airdrops": [ { "chainId": 84532, "tokenId": "spiceUsd", "amount": "200000000000000000000" } ], "tag": "optional-label" } ``` | Field | Type | Required | Description | | - | - | - | - | | `airdrops` | `array` | Yes | Non-empty array of airdrop requests | | `airdrops[].chainId` | `number` | Yes | Target chain ID | | `airdrops[].tokenId` | `string` | Yes | Token identifier (see below) | | `airdrops[].amount` | `string` | Yes | Amount in base units, as a string | | `tag` | `string` | No | Optional label for the batch | ## Supported Token IDs | Token ID | Description | Example chains | | - | - | - | | `spiceUsd` | Test USD | Base Sepolia (84532), Arbitrum Sepolia (421614) | | `mWBTC` | Test WBTC | Testnet chains | Verified example token addresses for `spiceUsd`: Base Sepolia `0xf370dC3765f81aC9dD2FEBd59Fb4e710330B0BC8`, Arbitrum Sepolia `0xBeB51deb2018b67b35d5695Fd15bb30D452c7868`. ## Response **Success (200)** returns the per-request results (amount airdropped and the airdrop transaction hash for each entry). ## Error Responses **400 Bad Request** (validation), for example: ```json theme={null} { "success": false, "error": { "message": "airdrops must be a non-empty array" } } ``` ```json theme={null} { "success": false, "error": { "message": "airdrops[0].tokenId unknownToken is not supported" } } ``` ## Example ```bash theme={null} curl -X POST https://tx-submission-api.spicenet.io/airdrop \ -H "Content-Type: application/json" \ -d '{ "airdrops": [ { "chainId": 84532, "tokenId": "spiceUsd", "amount": "200000000000000000000" } ] }' ``` ## Whitelist A separate `POST /airdrop/whitelist` endpoint records a wallet against a tag: ```bash theme={null} curl -X POST https://tx-submission-api.spicenet.io/airdrop/whitelist \ -H "Content-Type: application/json" \ -d '{ "wallet": "0x...", "tag": "campaign-x" }' ``` ## Notes * Testnet only, not available on mainnet. # Health Check Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/health Check API server status # GET /health Simple health check endpoint to verify the API server is running. ## Endpoint ```http theme={null} GET /health ``` ## Response **Success (200)** ```json theme={null} { "success": true, "message": "tx-submission-api server running...", "gitSha": "abc123def456..." } ``` | Field | Type | Description | | - | - | - | | `success` | `boolean` | Always `true` if server is healthy | | `message` | `string` | Status message | | `gitSha` | `string` | Git commit SHA of deployed version | ## Example ```bash theme={null} curl /health ``` ## Use Cases * **Monitoring**: Health check for load balancers and uptime monitoring * **Debugging**: Verify which version is deployed via `gitSha` # GET /intent/{intentId}/step/{stepId}/status Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/intent-step-status Get the status for a specific intent step and auto-advance behavior # GET /intent//step//status > [!WARNING] > > **Deprecated.** This route is not served by the current relayer. Get step status at > [`GET /actions/{actionId}/intents/{intentIndex}/steps/{stepIndex}`](/api-reference/endpoint/actions-execute) > instead. This page is kept for historical reference only. Returns the status of a specific chain batch (step) for an intent. If the step is in `created` state, the server triggers execution asynchronously. If the step is `success` or `reverted`, the server also triggers the next step. ## Endpoint ```http theme={null} GET /intent/{intentId}/step/{stepId}/status ``` ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `intentId` | `string` | Yes | The unique intent identifier | | `stepId` | `number` | Yes | The chain batch index \[0, n) for this intent | ## Response ### Success Response (200) ```json theme={null} { "success": true, "data": { "status": "created | executing | success | reverted | error", "transactionHash": "0x...", "intentId": "0x..." } } ``` * `transactionHash` is present when available (e.g., after a transaction is sent). ### Not Found (404) ```json theme={null} { "success": false, "error": { "message": "Chain authorization step not found" } } ``` ## Notes * When `status` is `created`, the server will start execution for this step in the background. * When `status` is `success` or `reverted`, the server will attempt to execute the next step. * Use this endpoint to poll for progress between executions. # POST /intent/{intentId}/step/{stepId} Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/intent-steps Execute a specific intent step and return the execution results # POST /intent//execute/ > [!WARNING] > > **Deprecated.** This route is not served by the current relayer. Execute a step at > [`POST /actions/{actionId}/intents/{intentIndex}/steps/{stepIndex}`](/api-reference/endpoint/actions-execute) > instead. This page is kept for historical reference only. Execute a specific intent step and return the execution results. ## Endpoint ```http theme={null} POST /intent/{intentId}/execute/{stepId} ``` ## Description This endpoint executes a specific step in an intent workflow. Each call performs one unit of work, triggering checks, sending transactions, and returning the execution results. This is an active execution endpoint that modifies state and performs side effects. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `intentId` | `string` | Yes | The unique intent identifier (signature of chainBatches) | | `stepId` | `number` | Yes | The step index \[0, n) representing the chain batch array index | ## Request Body ```json theme={null} { "executeOptions": { "dryRun": false, "gasLimit": 500000 } } ``` ### Request Parameters | Field | Type | Required | Description | | - | - | - | - | | `executeOptions.dryRun` | `boolean` | No | If true, simulate execution without committing | | `executeOptions.gasLimit` | `number` | No | Gas limit for transaction execution | ## How It Works * **Intent ID**: The signature of the chainBatches (similar to Solana's approach) * **Step ID**: Integer representing the index of the chain batch in the array * **Execution**: Calling this endpoint triggers the actual work for that step * **Unit of Work**: Each call performs one discrete unit of work in the intent workflow ## Response ### Success Response (200) ```json theme={null} { "success": true, "data": { "intentId": "0xabcdef1234567890...", "stepId": 0, "status": "completed", "transactionHash": "0x1234567890abcdef...", "chainId": 1, "executionResults": { "success": true, "gasUsed": 21000 } } } ``` ### Response Fields | Field | Type | Description | | - | - | - | | `intentId` | `string` | The intent identifier | | `stepId` | `number` | The executed step index | | `status` | `string` | Execution status | | `transactionHash` | `string` | Hash of the execution transaction (if applicable) | | `chainId` | `number` | Chain ID where the step was executed | | `executionResults` | `object` | Results of the step execution | ### Step Status | Status | Description | | - | - | | `pending` | Step is waiting to be executed | | `executing` | Step is currently being executed | | `success` | Step executed successfully | | `error` | Step execution failed | | `reverted` | Step transaction reverted on-chain | | `skipped` | Step was skipped due to conditions | ## Error Responses ### 400 Bad Request ```json theme={null} { "success": false, "error": { "message": "Invalid stepId: must be integer between 0 and n-1" } } ``` ### 404 Not Found Note: The POST execution endpoint currently returns 500 for most failures. Use the GET status endpoint for 404 (not found) semantics. ### 500 Internal Server Error ```json theme={null} { "success": false, "error": { "message": "Failed to execute intent step", "code": "EXECUTION_ERROR" } } ``` ## Example Request ```bash theme={null} curl -X POST /intent/0xabcdef1234567890.../execute/0 \ -H "Content-Type: application/json" \ -d '{ "executeOptions": { "dryRun": false, "gasLimit": 500000 } }' ``` ## Example Usage ```bash theme={null} # Execute the first step of an intent curl -X POST /intent/0xabcdef1234567890.../execute/0 \ -H "Content-Type: application/json" \ -d '{"executeOptions": {"dryRun": false}}' # Response shows execution results { "success": true, "data": { "intentId": "0xabcdef1234567890...", "stepId": 0, "status": "completed", "transactionHash": "0x1234567890abcdef...", "chainId": 1, "executionResults": { "success": true, "gasUsed": 21000 } } } # Execute the next step curl -X POST /intent/0xabcdef1234567890.../execute/1 \ -H "Content-Type: application/json" \ -d '{"executeOptions": {"dryRun": false}}' # Continue until all steps are complete ``` ## Workflow Pattern 1. **Submit Intent**: Use `POST /transaction/submit` to create the intent 2. **Execute Steps**: Call `POST /intent/{id}/execute/{i}` for each step i = 0, 1, 2, ... 3. **Check Results**: Each response contains the execution results for that step 4. **Continue**: Execute steps sequentially until the intent is complete ## Notes * Steps must be executed in order (0, 1, 2, ...) * Each step corresponds to a chain batch in the original array * The endpoint performs actual execution with side effects (not idempotent) * Failed steps may be retryable depending on the failure reason * Intent data is persisted in the database for reliable execution tracking * Use `dryRun: true` to simulate execution without committing changes # Spice Balances Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/spice-deposit Query a user's Spice balances ## Endpoint | Method | Endpoint | Description | | - | - | - | | `GET` | `/wallets/:address/balance` | Get a user's Spice balances | > [!NOTE] > > Deposits are not recorded through a manual endpoint. A user funds their Spice account through the > deposit flow (the `SpiceDeposit` component, or the escrow-deposit path of a `POST /actions` intent), > and the balance below reflects the result. There is no `POST /spicedeposit`. *** ## GET /wallets/:address/balance Returns the user's Spice (off-chain) balances: what they have deposited into escrow and can spend on gasless actions. This is the server ledger, not on-chain wallet balances. ### Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `address` | `string` | Yes | User wallet address | ### Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `receiptToken` | `string` | No | Filter to specific receipt token(s) | | `chainToken` | `string` | No | Filter to specific chain token(s) | ### Response **Success (200)** ```json theme={null} { "balances": [ { "receiptToken": "0x...", "token": "0x...", "chainId": 8453, "amount": "1000000000000000000" } ] } ``` Each entry is one balance line; `amount` is a string in the token's base units. ### Example ```bash theme={null} curl https://tx-submission-api.spicenet.io/wallets/0x742d35Cc6634C0532925a3b844e4B7db0D6d8E5c/balance ``` > [!NOTE] > > The SDK exposes this through `useSpiceAssets` and the exported `fetchBalances` / `useWalletBalances` > helpers, which honour the configured `apiUrl`. Prefer those over calling the endpoint directly. # POST /transaction/submit Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/transaction-submit Submit a cross-chain transaction with EIP-7702 authorization and intent execution # POST /transaction/submit > [!WARNING] > > **Deprecated.** This endpoint is not served by the current relayer. Use > [`POST /actions`](/api-reference/endpoint/actions-create) instead, which is the unified entry point for > creating intents. This page is kept for historical reference only. Submit a cross-chain transaction with EIP-7702 authorization and intent execution. ## Endpoint ```http theme={null} POST /transaction/submit ``` ## Description This endpoint allows you to submit transactions that will be executed across multiple chains. The system validates the transaction, saves it to the database, and returns tracking information. Execution is performed asynchronously by a solver. ## Request Body ```typescript theme={null} { address: Address; // User wallet address authorization: [{ chainId: number; address: string; nonce: number; r: string; s: string; yParity: number; }]; intentAuthorization: { signature: string; chainBatches: Array<{ hash: string; chainId: number | bigint; calls: Array<{ to: string; value: string | number | bigint; data: string }>; recentBlock?: number | bigint; }>; }; tokenTransfers: Array>; } ``` ### Parameters | Field | Type | Required | Description | | - | - | - | - | | `address` | `string` | Yes | User wallet address | | `authorization` | `array` | Yes | Array of EIP-7702 authorization objects | | `authorization[].chainId` | `number` | Yes | Chain ID for the authorization | | `authorization[].address` | `string` | Yes | Address being authorized | | `authorization[].nonce` | `number` | Yes | Authorization nonce | | `authorization[].r` | `string` | Yes | ECDSA signature R value | | `authorization[].s` | `string` | Yes | ECDSA signature S value | | `authorization[].yParity` | `number` | Yes | ECDSA signature Y parity | | `intentAuthorization.signature` | `string` | Yes | Intent signature (becomes intent ID) | | `intentAuthorization.chainBatches` | `array` | Yes | Array of chain batches with calls | | `tokenTransfers` | `array` | Yes | 2D array of transfers per chain batch | ## Response ### Success Response (200) ```json theme={null} { "success": true, "data": { "status": "created", "intentId": "0xabcdef1234567890...", "transactionId": "uuid-generated-id" } } ``` ### Response Fields | Field | Type | Description | | - | - | - | | `status` | `string` | Submission status (always `created` on success) | | `intentId` | `string` | Intent identifier (same as intentAuthorization.signature) | | `transactionId` | `string` | Generated transaction ID for tracking | ## Error Responses ### 400 Bad Request ```json theme={null} { "success": false, "error": { "message": "Missing authorization or intentAuthorization" } } ``` ```json theme={null} { "success": false, "error": { "message": "Token transfers do not match calls" } } ``` > [!NOTE] > > The API validates that `tokenTransfers` match the `calls` in each chain batch. Each transfer must correspond to the expected call data. ### 500 Internal Server Error ```json theme={null} { "success": false, "error": { "message": "Internal server error" } } ``` ## Example Request ```bash theme={null} curl -X POST /transaction/submit \ -H "Content-Type: application/json" \ -d '{ "authorization": [ { "chainId": 1, "address": "0x742d35Cc6634C0532925a3b8D8D428C83c76a4B", "nonce": 42, "r": "0x1234567890abcdef...", "s": "0xfedcba0987654321...", "yParity": 1 }, { "chainId": 137, "address": "0x742d35Cc6634C0532925a3b8D8D428C83c76a4B", "nonce": 43, "r": "0xabcdef1234567890...", "s": "0x0987654321fedcba...", "yParity": 0 } ], "intentAuthorization": { "signature": "0xabcdef1234567890...", "chainBatches": [ { "hash": "0x9876543210fedcba...", "chainId": 1, "calls": [ { "to": "0x123...abc", "value": 0, "data": "0xa9059cbb..." } ], "recentBlock": 18500000 } ] } }' ``` ## Workflow 1. **Transaction Validation**: Validates authorization array and intentAuthorization are provided and properly formatted 2. **Database Storage**: Saves the intent and all chain authorizations to the database for reliable tracking 3. **Chain Broadcast**: Broadcasts the transaction to the appropriate blockchain network 4. **Response**: Returns transaction hash and intent ID for subsequent step execution ## Next Steps After successful submission: 1. **Track Progress**: Use the returned `intentId` to execute individual steps 2. **Execute Steps**: Call `POST /intent/{intentId}/execute/{stepId}` for each step (0, 1, 2, ...) 3. **Monitor Results**: Each step execution returns detailed results and status ## Example Workflow ```bash theme={null} # 1. Submit the intent with multiple authorizations curl -X POST /transaction/submit -d '{ "authorization": [ {"chainId": 1, "address": "0x742...", "nonce": 42, ...}, {"chainId": 137, "address": "0x742...", "nonce": 43, ...} ], "intentAuthorization": {...} }' # Returns: {"success": true, "data": {"transactionHash": "0x123...", "intentId": "0xabc..."}} # 2. Execute step 0 curl -X POST /intent/0xabc.../execute/0 -d '{"executeOptions": {...}}' # Returns: execution results for first chain authorization # 3. Execute step 1 curl -X POST /intent/0xabc.../execute/1 -d '{"executeOptions": {...}}' # Returns: execution results for second chain authorization # Continue for all steps... ``` ## Notes * Intent ID is derived from `intentAuthorization.signature` * Authorization array can contain multiple EIP-7702 authorizations for different chains/addresses * All data is persisted to database for reliability and recovery * Step execution is triggered separately via the intent execution endpoint * Failed submissions can be debugged using the transaction hash * The system supports resumable execution for complex multi-step intents # Handle Transaction Source: https://spiceflow-docs.spicenet.io/api-reference/endpoint/venue-tx-hashes Register an on-chain transaction hash for the solver to process ## Endpoint ```http theme={null} POST /transactions/handle/evm ``` Register an EVM transaction hash so the relayer inspects its receipt and records the relevant transfers (for example, an escrow deposit) against the user's Spice balance. This replaces the older `/venue-tx-hashes` endpoints. ## Request Body ```json theme={null} { "chainId": 8453, "hash": "0x..." } ``` | Field | Type | Required | Description | | - | - | - | - | | `chainId` | `number` | Yes | Chain the transaction was mined on | | `hash` | `Hex` | Yes | Transaction hash | ## Response **Success (200)** returns the processing result. The relayer fetches the transaction receipt on the given chain, filters the relevant transfer logs, and records them. ## Example ```bash theme={null} curl -X POST https://tx-submission-api.spicenet.io/transactions/handle/evm \ -H "Content-Type: application/json" \ -d '{ "chainId": 8453, "hash": "0x..." }' ``` > [!NOTE] > > The old `POST/GET /venue-tx-hashes` endpoints are no longer served. Use `POST /transactions/handle/evm`. # Delegate Contract Source: https://spiceflow-docs.spicenet.io/deep-dive/delegate A technical overview of our delegate contract, the core of the EIP-7702 implementation responsible for executing user intents on-chain. ## The `Delegate` Contract: The On-Chain Execution Engine The `Delegate` contract is the on-chain component that receives and executes the user's signed instructions. It is the implementation that an EOA delegates its authority to. It acts as the user's temporary agent on the target chain, ensuring that only the actions specified in the user's signed `Intent` are executed. ### Key Functions and Concepts * **`Intent` and `ChainBatch` Structs**: These structs define the core data structure. An `Intent` is a collection of `ChainBatch`es, and each batch is a set of calls for a specific `chainId`. This is how the contract can handle multi-chain instructions. * **`execute(Intent calldata intent)`**: This is the primary function for executing a user's signed intent. The most critical part is the signature verification: `if (recovered != address(this)) revert InvalidSignature(recovered, address(this));`. When a user's EOA is delegated to this contract, `address(this)` *is* the user's address for the duration of the transaction. This check ensures that the person who signed the intent is the same person whose authority is being used to execute it. * **`selfExecute(Call[] calldata calls)`**: This function is what the solver calls. The key here is the `if (msg.sender != address(this)) revert InvalidAuthority();` check. The solver submits a transaction that includes the user's EIP-7702 authorization. This authorization makes the solver's EOA *become* the `Delegate` contract. Therefore, when the solver calls `selfExecute` on itself, the `msg.sender` is `address(this)`, and the check passes. This allows the solver to bundle the user's intent with other calls, like fee payments or refunding the user. * **Replay Protection (`_registerSignatureHash`)**: To prevent a malicious actor from re-submitting an old, completed transaction, the contract keeps a history of recently executed `ChainBatch` hashes. It stores these hashes in buckets corresponding to the `recentBlockNumber` provided in the batch. It will revert if it finds the same hash has been used in a recent block, preventing double-spending and replay attacks. The history is limited to `16` blocks to keep storage costs manageable. * **Timeliness Checks**: The contract also ensures that a `ChainBatch` is executed within a reasonable time window. It reverts if the `recentBlock` is in the future (`TooEarly`) or more than 16 blocks in the past (`SignatureExpired`). ### Overall Flow ![Delegate Contract Flow](https://mintcdn.com/ter-e1ca54a7/bSxPS04-n9iQl8aq/assets/delegate-flow.png?fit=max\&auto=format\&n=bSxPS04-n9iQl8aq\&q=85\&s=e055502f48931857d3f98bcf478131f4) # EIP-7702: Account Abstraction via Temporary Delegation Source: https://spiceflow-docs.spicenet.io/deep-dive/eip7702 Technical deep dive into EIP-7702 and how Spice Flow leverages EIP-7702-powered delegation for atomic transaction execution. ## EIP-7702: Temporary Account Delegation **EIP-7702** is a new and powerful Ethereum Improvement Proposal, which came was first created in May 2024, that allows an *Externally Owned Account (EOA)* (a regular user wallet) to temporarily act as a smart contract on EVM chains. It achieves this by letting a user sign an `authorization` that delegates their account's authority to a specific smart contract implementation. **Why is this important?** Instead of a user sending multiple transactions across different chains, they can sign a single **EIP-7702** `authorization` for each chain. This authorization is like giving a one-time permission slip to our system to perform specific actions on that chain. It's powerful because: * **It's Non-custodial**: The user never gives up control of their private keys. The authorization is a signature, not a key transfer. * **It's Secure**: The authorization is for a specific contract and is only valid for one chain, minimizing risk. * **It's atomic**: The user can bundle multiple actions into a single intent, which is then executed as a single transaction on each chain, if all actions are valid, if any action fails, the entire transaction reverts. For example, a user can sign an intent that includes swapping tokens on one chain and bridging assets to another chain. If either action fails, neither action is executed, ensuring the user doesn't end up in a partial state. * **It Enables Gas Sponsorship**: Because the user delegates execution, a third-party *"solver"* can submit the transaction and pay the gas fees on the user's behalf. * **It Enables Batching**: A solver can bundle multiple actions into a single on-chain transaction. For example, executing a user's intent *and* paying the user back for their initial deposit. * **It Allows Privileged Execution**: The contract can perform actions that a regular EOA cannot, such as interacting with other smart contracts, executing complex logic, or even paying gas fees in tokens other than ETH. * **It's compatible with EIP-4337**: EIP-7702 can be used in conjunction with EIP-4337 (Account Abstraction) to provide a seamless user experience. For example, a user could sign an EIP-7702 authorization that delegates to an EIP-4337 smart contract wallet, allowing for even more complex interactions. ## Some specifications ### Parameters | Parameter | Value | | - | - | | `SET_CODE_TX_TYPE` | `0x04` | | `MAGIC` | `0x05` | | `PER_AUTH_BASE_COST` | `12500` | | `PER_EMPTY_ACCOUNT_COST` | `25000` | A new **EIP-2718** transaction known as the "set code transaction" is introduced with **EIP-7702**, where the *TransactionType* is set to `0x04` and *TransactionPayload* is the RLP serialization of the following: ``` rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, signature_y_parity, signature_r, signature_s]) authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...] ``` * the fields inside the `rlp` of the outer transaction follow the same semantics as *EIP-4844*. * The `signature_y_parity, signature_r, signature_s` elements of this transaction represent a *secp256k1* signature over `keccak256(SET_CODE_TX_TYPE || TransactionPayload)`. * `authorization_list` is a list of authorizations, each containing: * `chain_id`: the chain ID where the authorization is valid. * `address`: the address of the contract to which authority is being delegated. * `nonce`: a unique number to prevent replay attacks. * `y_parity`, `r`, `s`: components of the ECDSA signature. * The authorization\_list is a list of tuples that indicate what code the signer of each tuple desires to execute in the context of their EOA. The transaction is considered invalid if the length of authorization\_list is zero. ## Behavior The authorization list is processed before the execution of the transaction, but after the sender's nonce is incremented. For each `[chain_id, address, nonce, y_parity, r, s]` tuple, perform the following: 1. Verify the chain ID is 0 or the ID of the current chain. 2. Verify the `nonce` is less than `2**64 - 1`. 3. Let `authority = ecrecover(msg, y_parity, r, s)`. * Where `msg = keccak(MAGIC || rlp([chain_id, address, nonce]))`. * Verify `s` is less than or equal to `secp256k1n/2`, as specified in EIP-2. 4. Add `authority` to `accessed_addresses`, as defined in EIP-2929. 5. Verify the code of `authority` is empty or already delegated. 6. Verify the nonce of `authority` is equal to `nonce`. 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` is not empty. 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation indicator. * If `address` is `0x0000000000000000000000000000000000000000`, do not write the delegation indicator. Clear the account’s code by resetting the account’s code hash to the empty code hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`. 9. Increase the nonce of `authority` by one. In case, any step fails, the processing of that tuple is halted immediately, and skipped to the next tuple in the authorization list. Note, if transaction execution results in failure (e.g. any exceptional condition or code reverting), the processed delegation indicators is not rolled back. *** ### Sources * [https://eip7702.io/](https://eip7702.io/) * [https://eips.ethereum.org/EIPS/eip-7702](https://eips.ethereum.org/EIPS/eip-7702) # Best Practices Source: https://spiceflow-docs.spicenet.io/guides/best-practices Security guidelines, common pitfalls, and best practices for developers integrating with Spice Flow # Best Practices This document outlines best practices, common pitfalls, and security considerations for developers integrating with the Transaction Submission API. Following these guidelines will help ensure a secure, efficient, and reliable integration. ## SDK Integrations For SDK 4.19.0, use stock components or `useSpiceSupply`. Render its readiness, fees and status; do not add a separate fee estimator, executor or balance poller. Preserve source owner/location with `assetPositionId`, and keep balances visible during background refresh. Use the builder's destination amount and beneficiary when encoding protocol calls. An uncertain financial outcome is not a reason to resubmit automatically. Use `refreshStatus()` to reconcile the original action, and check balances and receipts. `retryFinalization()` only retries failed application work after confirmation; it does not send another transaction. The following lower-level API guidance does not require SDK consumers to implement their own signatures or transaction tracking. See [Migration](/sdk/migration). ## General Principles * **Validate Inputs**: Always validate and sanitize any inputs from users or external systems that are used to construct calls within an intent. Never trust user input directly when forming transaction data. * **Least Privilege**: Construct intents with the minimum permissions necessary to achieve the desired outcome. For example, if swapping tokens, set the exact amount for approval rather than an unlimited approval. * **Idempotency**: Design your submission logic to be idempotent where possible. If you encounter a network error when submitting an intent, you should be able to safely retry the submission without creating duplicate intents. ## Security Best Practices * **Signature Verification**: The security of the entire system relies on cryptographic signatures. Ensure you are using trusted libraries (like `viem` or `ethers`) to produce EIP-7702 authorizations and intent signatures. * **Protect User Private Keys**: In any front-end integration, the user's private key must remain entirely within their control. Your application should only ever request signatures for authorizations and intents, never the key itself. * **Chain ID and Contract Address Validation**: Double-check that you are targeting the correct `chainId` and `delegate` contract address when creating EIP-7702 authorizations. A mismatched chain ID can lead to failed transactions or unexpected errors. ## Common Errors and Troubleshooting * **`CallReverted`**: This means one of the calls within your `ChainBatch` failed on-chain. * **Troubleshooting**: 1. Check the `to`, `value`, and `data` for the failing call. Is the address correct? Is the function signature in the data correct? 2. Ensure the `Delegate` contract has the necessary approvals (e.g., ERC20 `approve`) to perform the action on behalf of the user. Approvals should be included as one of the first calls in your `ChainBatch`. ## Integration Guidelines ### Frontend Integration * Use established Web3 libraries like `viem`, `ethers`, or `wagmi` for wallet connections and signature generation * Always show users what they're signing before requesting signatures * Implement proper loading states during transaction submission and execution * Handle network errors gracefully with retry mechanisms ### Backend Integration * Implement proper error handling and logging for all API calls * Use database transactions when updating your application state based on intent execution * Consider implementing webhooks or polling for intent status updates * Store transaction hashes for audit trails and user support ### Performance Optimization * Batch multiple operations into single intents when possible * Use appropriate gas limits to avoid failed transactions * Consider implementing retry logic with exponential backoff for network errors * Cache frequently accessed data like chain configurations ## Development Workflow 1. **Test on Testnets**: Always test your integration thoroughly on testnets before going to production 2. **Use Dry Runs**: Utilize the `dryRun` option in step execution to validate your intents before committing 3. **Monitor Gas Usage**: Track gas consumption patterns to optimize your intents 4. **Handle Edge Cases**: Plan for scenarios like network congestion, failed steps, and partial execution ## Security Checklist * [ ] User private keys never leave their device * [ ] All user inputs are validated and sanitized * [ ] Chain IDs and contract addresses are verified * [ ] Signature verification is implemented correctly * [ ] Error handling doesn't leak sensitive information * [ ] Rate limiting is implemented to prevent abuse * [ ] Audit trails are maintained for all transactions ## Support and Resources > **[Deep Dive: EIP-7702](/deep-dive/eip7702)** > > Technical details about EIP-7702 delegation > **[Native Swap Guide](/guides/native-swap)** > > Complete implementation example > **[Frontend Integration](/guides/custom-ui)** > > React.js integration guide > **[How It Works](/how-it-works)** > > Understanding the technical flow # Cash Funding Source: https://spiceflow-docs.spicenet.io/guides/cash-funding Fund native account actions from matching balances or multiple account positions SDK 4.19.0 supports two opt-in funding patterns through `useSpiceSupply`. Both keep quoting, conversion, fees, spend caps and execution inside the SDK. Account balance display alone is not a spendable quote; use the controller's availability and readiness fields. ## Combine Matching Balances Set `combineSourceBalances: true` on `useSpiceSupply`, `SpiceSupply` or `useSpiceLock` when the action should use eligible matching wallet and escrow positions together. It defaults to false. Keep the selected full `Asset` and its amount. The SDK matches the same owner and token mapping, accounts for reservations, avoids counting a position twice, and builds the necessary deposit or withdrawal allocation. It does not combine arbitrary symbols, unrelated owners or stale balances. Use `assets.getAvailableBalance(asset)` and `amount.availableBalance` instead of adding displayed balances yourself. `max()` includes funding and fee requirements and can return `null`. ## Purchase from Account Cash Use `fundingSources` when the UI presents one purchase budget funded from several nominated account positions, without choosing a single source token. The contract is: | Input | Meaning | | - | - | | `fundingSources.token` | Supported USDC token on the destination chain; budget denomination | | `fundingSources.positionIds` | Physical account positions nominated with `assetPositionId(asset)` | | `selectedAsset` | Must be `null` | | `paymentAmount` | Total budget string in the funding token's units, despite this field's usual fixed-output role | | `amountDirection` | `"input-inclusive"`; also the default when `fundingSources` is present | | `destinationToken` | Token your protocol calls consume, on the same chain as the funding token | | `buildActionCalls` | Protocol calls using the SDK's destination-token bigint after conversion and fees | This mode requires the embedded 7702 account. It accepts eligible embedded-wallet positions and mapped Spice balances owned by that account, subject to source-chain/token policy and available routes. It cannot be combined with a selected asset, fee-only actions, `actionInput`, `actionProceeds` or `actionExecution`. It does not pool external wallets into the account purchase. The SDK uses credited Spice Cash before wallet conversion/deposit. It quotes source conversions, funding, destination conversion and fees within the common USDC budget. Nominating a position does not force it into the accepted plan if it is ineligible or unnecessary. ### Controlled Integration This hook adapter accepts the host's budget, nominated inventory positions, and protocol builder. Mount it under your existing provider stack and render the returned controller as in [Custom UI Integration](/guides/custom-ui). Pass physical inventory positions rather than a synthetic Cash display row. ```tsx theme={null} import { assetPositionId, useSpiceSupply, type Asset, type SpiceSupplyProps, } from "@spicenet-io/spiceflow-ui"; export function useCashPurchase({ budget, positions, destinationToken, fundingToken, buildActionCalls, }: { budget: string; positions: Asset[]; destinationToken: SpiceSupplyProps["destinationToken"]; fundingToken: SpiceSupplyProps["destinationToken"]; buildActionCalls: SpiceSupplyProps["buildActionCalls"]; }) { return useSpiceSupply({ destinationToken, selectedAsset: null, fundingSources: { token: fundingToken, positionIds: positions.map(assetPositionId), }, paymentAmount: budget, amountDirection: "input-inclusive", buildActionCalls, }); } ``` Use `flow.assets.fundingSources` for the eligible nominated inventory and `flow.feeQuote?.fundingPlan` for the accepted allocation. Each plan source has its identity, token units, source spend cap, available/reserved amount and minimum output. Do not encode a second funding plan in the host. Show `flow.fees.preview`, wait for `flow.canExecute`, then call `flow.submit()`. `flow.max()` returns a budget string in the common funding-token units. In this mode the selected asset is null, so do not format all source debits with one selected asset's decimals; use each plan source's own decimals. Conversions can change the destination amount without changing the total budget. ## Pending Actions and Recovery Funding belongs to the original action. A pending funding leg is not permission to start a new purchase or repeat a deposit. Render `status.pending`, `status.recoveryChecking` and `status.recoveryError`; call `refreshStatus()` to check the original action. Continue using `canExecute` for submission readiness. See [Recovery and completion](/sdk/migration#recovery-and-completion). # Conditional Intents and Limit Orders Source: https://spiceflow-docs.spicenet.io/guides/conditional-intents Prepare, review, sign and manage scheduled actions with the stable SDK SDK 4.19.0 exposes `useSpiceConditionalIntents` and `useSpiceLimitOrders`. They are separate APIs with separate lifecycle objects. Both require a ready embedded wallet in 7702 mode and a configured backend that supports the requested service. Registration is not transaction completion. ## Conditional Intents `useSpiceConditionalIntents()` prepares a fixed action for a configured trigger service and evaluator. The host defines the trigger specification and protocol calls; the SDK binds the reviewed calls and trigger bytes to the signatures and registration request. ### Draft Fields | Field | Type / meaning | | - | - | | `clientId`, `serviceId` | Strings identifying your configured client and conditional service | | `evaluator` | Evaluator address authorized for the trigger | | `triggerSpecification` | Exact hex-encoded trigger specification understood by your service; not an arbitrary human-language prompt | | `semantics` | `"state"` or `"event"` | | `maxSignalAgeSeconds` | Bigint freshness bound, 1 through 86,400 seconds | | `validAfter`, `expiresAt` | Unix seconds as bigints; valid-after must not exceed expiry; expiry must be future and within 30 days | | `chainBatches` | Fixed nonempty action batches for `prepare`; each batch needs a valid chain and calls | `prepare(draft)` does not turn arbitrary protocol calls into a funded or fee-quoted action. Supply complete executable batches, including required funding and fee calls, and review their validity at execution time. For the supported swap path use `prepareSwap`, which owns its swap/funding/fee plan. `prepareSwap(draft)` replaces `chainBatches` with `chainId`, `tokenIn`, `tokenOut`, `amountIn`, `minimumOutput`, and an `AerodromeVenueConfig` in `venue`. Amounts are raw token-unit bigints. `amountMode` is `"fixed-input"` or `"input-inclusive"`. Review the returned `swapAmountIn`, `fees`, `feeFundingSource` and `feeSourceAmount`; do not infer the executable swap input from the gross budget. A zero minimum output does not protect the fill price. Show any `actionSummary` disclosure. ### Review, Sign and Register Keep the hook mounted throughout preparation, review, signing and registration. Use the exact object returned by that hook; a serialized, cloned or independently reconstructed object cannot replace it. A wallet/provider/API change requires a new preparation. ```tsx theme={null} import { useState } from "react"; import { useSpiceConditionalIntents, type ConditionalIntentDraft, type PreparedConditionalIntent, } from "@spicenet-io/spiceflow-ui"; export function useConditionalReview() { const intents = useSpiceConditionalIntents(); const [review, setReview] = useState(null); async function prepare(draft: ConditionalIntentDraft) { const prepared = await intents.prepare(draft); setReview(prepared); return prepared; } // Call only after rendering the prepared action and obtaining user confirmation. async function confirm() { if (!review) throw new Error("Prepare and review an action first"); const signed = await intents.sign(review); setReview(signed); return intents.register(signed); } return { prepare, review, confirm, get: intents.get, list: intents.list, cancel: intents.cancel }; } ``` Render `actionSummary` (chain, target, value, selector and available swap bounds), the trigger and expiry, and the returned fee data before calling `confirm`. The prepared object reports `additionalSignatureCount: 2` plus `delegationAuthorizationCount`. These are signature requirements, not a promise of a particular number of wallet popups. The trigger is enforced by the conditional-intent service; it is not an on-chain condition added to every protocol call. Registration does not guarantee a later fill: balances, liquidity, allowances, contract state and the authorization's validity must still permit execution. ### Read and Cancel | Method | Behavior | | - | - | | `authorizeRead(serviceId)` | Obtain a cached wallet-authorized read token; can request a signature | | `get(id, serviceId)` | Read one intent; obtains read authorization as needed | | `list(serviceId, { limit?, cursor? })` | Return `intents` and optional `nextCursor` | | `cancel(id, serviceId)` | Sign and submit cancellation; inspect the returned status | Status reads can prompt for a read signature. Keep polling and cancellation tied to the current wallet and service. Cancellation cannot undo a transaction already executed; reconcile returned status and receipts before presenting funds as available or creating a replacement. ## Limit Orders `useSpiceLimitOrders({ combineSourceBalances?: boolean })` handles supported limit swaps funded from account Cash. Combined funding is off by default; opting in lets the SDK fund a shortfall from an eligible account-wallet balance. It may submit a real deposit during placement before registering the order, so placement is not a read-only call. | `LimitOrderInput` field | Meaning | | - | - | | `chainId`, `tokenIn`, `tokenOut` | Supported execution chain and token addresses | | `grossAmount` | Total raw input-token budget including execution fees | | `minimumOutput` | Positive raw output-token minimum | | `expiresAt` | Unix seconds as a **number**, in the future and within 30 days | | `venue` | Optional Aerodrome Slipstream configuration; the built-in Base configuration is the default | Use the methods in this order, with the same mounted hook: 1. Wait for `canPrepare`, then call `prepareOrder(input)`. 2. Render the returned `amountIn`, `minimumOutput`, `feeAmount`, `grossAmount` and any `funding` or `fundingWarning`. The funding fields use the original source asset's units; `grossAmount` is the escrow budget after any funding fee. 3. After user confirmation call `signOrder(prepared)` and retain its exact returned request. 4. Check `canPlace(request)` and call `placeOrder(request)`. 5. Read `getOrder(id)` or `listOrders(after?)`; cancel with `cancelOrder(id)`. Render `status.step`, `status.message`, `status.isExecuting`, `status.pending` and `status.error`. Call `refreshStatus()` after an uncertain funding or registration outcome; do not start a fresh funding sequence to work around a pending original order. Retain the original signed request while the hook remains mounted so the SDK can reconcile placement rather than deposit twice. `listOrders` returns `{ orders, nextCursor }`. Order statuses are `open`, `executing`, `filled`, `cancelled`, `expired`, `failed` and `needs_attention`. Inspect `actionId`, `transactionHash`, `lastError` and any balance warning. An open order is not a fill, and a reserved amount is not proof that all execution-time funding is available. # Custom UI Integration Source: https://spiceflow-docs.spicenet.io/guides/custom-ui Keep your own UI and let one SDK controller manage funding, quotes, fees and execution For `@spicenet-io/spiceflow-ui@4.19.0`, connect your existing selector, amount input and button to `useSpiceSupply`. It is the same controller used by the stock `SpiceSupply` component. Your app defines the destination contract calls and renders the returned state. The SDK owns asset inventory, route selection, fee estimates, Max, funding, signing and transaction completion. ## Provider Setup Mount the [provider stack](/quickstart#step-2-setup-providers) once above this panel. Reuse your existing Privy, wagmi and Query providers. Set `network="mainnet"` explicitly for production; the default remains `"testnet"`. SDK styles load automatically in the browser. ## Complete Example This panel deposits USDC into an ERC-4626 vault on Base. Pass the address of your vault, whose `asset()` must be Base USDC. For another protocol, replace only the destination token and the `buildActionCalls` body with that protocol's ABI and calls. ```tsx theme={null} "use client"; import { useState } from "react"; import { encodeFunctionData, erc20Abi, formatUnits, parseAbi, type Address } from "viem"; import { assetPositionId, useSpiceSupply, type Asset } from "@spicenet-io/spiceflow-ui"; const destinationToken = { chainId: 8453, address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Address, }; const vaultAbi = parseAbi([ "function deposit(uint256 assets, address receiver) returns (uint256 shares)", ]); export function SupplyPanel({ vault }: { vault: Address }) { const [selected, setSelected] = useState(null); const [amount, setAmount] = useState(""); const flow = useSpiceSupply({ destinationToken, selectedAsset: selected ? { asset: selected, amount } : null, buildActionCalls: (amountWei, ctx) => [ { to: ctx.destinationToken, value: 0n, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, amountWei] }), }, { to: vault, value: 0n, data: encodeFunctionData({ abi: vaultAbi, functionName: "deposit", args: [amountWei, ctx.beneficiary ?? ctx.userAddress], }), }, ], }); const preview = flow.fees.preview; return (
Supply USDC {flow.assets.loading &&

Loading balances…

} {flow.assets.displayAssets.map((asset) => ( ))} {preview &&

Estimated fees: {formatUnits(preview.totalFee ?? (preview.gasFee + preview.spicenetFee + (preview.bridgeFee ?? 0n)), preview.decimals)} {preview.symbol}

} {flow.amount.sourceRequiredAmount !== null && flow.selectedAsset &&

Maximum source debit: {formatUnits(flow.amount.sourceRequiredAmount, flow.selectedAsset.asset.decimals)} {flow.selectedAsset.asset.symbol}

}

{flow.status.statusMsg}

{(flow.status.error || flow.fees.error || flow.assets.error) &&

{flow.status.error || flow.fees.error || flow.assets.error}

} {flow.status.txHash &&

Transaction: {flow.status.txHash}

} {flow.status.recoveryError &&

{flow.status.recoveryError}

} {(flow.status.pending || flow.status.recoveryError) && } {flow.status.finalizationError && }
); } ``` The controller receives **three top-level inputs** in this example: `destinationToken`, `selectedAsset`, and `buildActionCalls`. Only `chainId` and `address` are required for the destination token; symbol and decimals resolve from SDK metadata or the chain. Your selected asset and amount are UI state, while the vault address and its calls remain your protocol's configuration. `enabled` defaults to `true`. For a modal that stays mounted while hidden, pass `enabled: isOpen`. Keep it true while fees load and use `canExecute` for button readiness. The default amount direction for this selected-asset example is `"input"`. ## Amount Semantics | `amountDirection` | Meaning | Typical use | | - | - | - | | `"input"` (default) | Entered source amount is principal; fees are added | Supply | | `"input-inclusive"` | Entered source amount is the total budget; fees are deducted | Lock or spend-all | | `"output"` | `paymentAmount` fixes the destination-token amount; source cost is quoted | Pay | For Pay, use this same hook with `amountDirection: "output"` and `paymentAmount`. There is no separate `useSpicePay` hook. Use `flow.submit()` for the configured direction, render `flow.fees.preview` with its own symbol/decimals, and enable the action only when `flow.canExecute`. `max()` returns a fee-aware source amount string or `null`; never replace `null` with the full balance. `buildActionCalls` receives a raw bigint in **destination-token units**, including the protected minimum when routing requires a swap. Use that argument, not the source input text, to encode approvals and protocol calls. The builder can run during planning; it must not send transactions or write application state. Put post-transaction updates in an awaited `onSuccess` callback. ## Wallets, Beneficiaries and Sources Keep the selected `Asset` object intact. Key rows with `assetPositionId(asset)` and show wallet/location alongside token and chain: the same token can exist in multiple wallets or in Spice balance. Funding can come from either connected wallet by default, even when account mode chooses a different position owner. Use `sourceChains`, `supportedSourceAssets`, `includeExternalWalletAssets`, or `includeCrossChainEmbeddedAssets` only when your product needs a restriction. For a fixed protocol beneficiary, pass `recipient` and use `ctx.beneficiary ?? ctx.userAddress` in the action calls. `ctx.executorAddress` identifies the executing wallet; it can differ from the beneficiary. External mode with its matching destination token on the same chain executes directly through the SDK. By default, routed external sources fund the embedded execution wallet. A contract that credits only `msg.sender` cannot automatically credit a different beneficiary; constrain that action to a compatible source/executor rather than changing the recipient silently. ## Completion and Errors Render controller loading, fee, readiness and status fields directly. Keep balances visible during `assets.refreshing`; reserve the initial loader for `assets.loading`. `onSubmit()` is an optional notification after the SDK accepts a submission and before wallet prompts, not an execution override. Await app work in `onSuccess`, such as refreshing a position or recording access. If that work fails, `retryFinalization()` retries the application update without resending the confirmed transaction. Accepted actions can continue on the backend when the configured API supports automatic continuation. Render `status.pending`, `status.recoveryChecking` and `status.recoveryError`, and offer `refreshStatus()` to check the original action. Closing the form or losing a response is not cancellation. The SDK does not persist a browser action journal; after a reload, do not assume your app's previous success callback will replay. Refresh application state from authoritative records. Supply/Pay reports the verified destination transaction hash; backend settlement may still continue. Use that destination result for your app update, rather than treating a funding transfer as completion. See [Recovery and completion](/sdk/migration#recovery-and-completion). For an app-owned pre-submit check, pass `beforeSubmit(review)`. The SDK awaits it and revalidates the operation before sending. Throw to reject the submission; use `submitDisabled` for host readiness. `onSubmit` is a notification and is not awaited for asynchronous validation. For an account Cash purchase without a single selected token, see [Cash funding](/guides/cash-funding). ## Fee-Only Actions and Withdrawals For a protocol action whose principal already sits in the executing wallet, use `feeOnlyAction`. Declare consumed tokens through `actionInput` and, if applicable, resulting funds through `actionProceeds`. The SDK chooses fee funding in `input`, `proceeds`, `balance` order by default; `feeFunding` can restrict that order. The builder must honor the amount it receives after any fee allocation. These are optional declarations for this action type, not requirements for ordinary Supply. For funded wallet actions on the destination chain, `actionProceeds: "output"` or an explicit nonempty `feeFunding` list opts into that hierarchy. Fixed input/output principal is reserved; input-inclusive budgets may pay fees. `feeFundingReserve` can identify principal unavailable for fees. Within a fee source tier, direct payment precedes conversion. Keep the returned fee quote authoritative; do not rebuild fee transfers in the host. Cross-chain and rollup principal routes retain their earlier funding requirements. `SpiceWithdraw` withdraws **Spice balance**, not a lending position. Protocol withdrawals use your protocol's own calls with the controller. `SpiceDeposit` and `SpiceWithdraw` retain their separate callback contracts; do not migrate their callbacks as if they were Pay or Lock. See [Hooks](/sdk/hooks) for the controller reference and [Migration to 4.19.0](/sdk/migration) for the changes from manual integration. # Native Swap Example Source: https://spiceflow-docs.spicenet.io/guides/native-swap Create a simple asset swap on Citrea backed by collateral on Ethereum sepolia > [!NOTE] > > This is a low-level API example, not the recommended SDK integration path. For SDK 4.19.0, > use [SpiceSupply](/sdk/components#spicesupply) or the [shared controller](/guides/custom-ui) > to let the SDK own quotes, fees, funding and execution. # Guide: Native Swap Example This guide provides a complete, code-first walkthrough for creating and submitting a swap transaction using the API. We will build a real-world example: swapping USD to cBTC on the Citrea blockchain backed by Sepolia ETH as collateral, using [Crest](https://x.com/crest_btc) as a liquidity source. ## The Goal Our goal is to create an "intent" that performs the following actions: 1. **On Sepolia Testnet**: A user deposits `0.0001` Sepolia ETH into an escrow EOA. 2. **On Citrea Testnet**: Using the value from the deposit, execute a swap for cBTC via `crest` contract. 3. **Finally**: The solver sends the principal amount of the deposit back to the user on Citrea. This entire process will be defined in a single intent, signed by the user once, and submitted to the API. ## Prerequisites You'll need `viem` to interact with chains, create authorizations, and sign messages. For frontend applications, you would need to use `privy` SDK to work with embedded wallets. ```typescript theme={null} import { createWalletClient, http, parseUnits, getAddress, formatEther } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { citreaTestnet, sepolia } from "viem/chains"; ``` ## Step 1: Setting up Accounts and Clients First, we define the accounts and blockchain clients we'll be working with. In a real application, the `user` would be the end-user interacting with your application, and their private key would be managed by their wallet. ```typescript theme={null} // A sample user account const user = privateKeyToAccount("0x..."); // The escrow contract address where the initial deposit is sent const escrowAddress = getAddress("0x..."); const publicSepoliaClient = createPublicClient({ chain: sepolia, transport: http(), }); const publicCitreaClient = createPublicClient({ chain: citreaTestnet, transport: http(), }); ``` ## Step 2: Define the Intent Parameters We'll define the amounts and generate a quote for the swap part of our intent. ```typescript theme={null} const depositAmount = parseUnits("0.0001", 18); // In a real app, you would call an external service like crest's /rfqt endpoint // to get a signed quote for the swap. const crestRfqt = await generateRfqt({ quoteId: "0x...", user: user.address, tokenIn: "usd", tokenOut: "bitcoin", amountIn: formatEther(depositAmount), amountOut: "100", // Targeting 100 USD expiry: Math.floor(Date.now() / 1000) + 60, // 1 minute expiry }); ``` ## Step 3: Create EIP-7702 Authorizations The core of the process is the user granting our system one-time authority on each chain. The user's wallet will sign an EIP-7702 authorization for each `delegate` contract on each chain. ```typescript theme={null} // Fetch the current nonce for the user on each chain const nonceSepolia = await publicSepoliaClient.getTransactionCount({address: user.address}); const nonceCitrea = await publicCitreaClient.getTransactionCount({address: user.address}); // Read the delegate contract per chain from the SDK; do not hardcode (it differs per chain). import { getDelegateContract } from "@spicenet-io/spiceflow-core"; const delegateAddressSepolia = getDelegateContract(sepolia.id); const delegateAddressCitrea = getDelegateContract(citreaTestnet.id); // User signs an authorization for Sepolia const userAuthSepolia = await user.signAuthorization({ address: delegateAddressSepolia, chainId: sepolia.id, nonce: nonceSepolia, }); // User signs another authorization for Citrea const userAuthCitrea = await user.signAuthorization({ address: delegateAddressCitrea, chainId: citreaTestnet.id, nonce: nonceCitrea, }); ``` These `authorization` objects are what a solver will use to execute transactions on the user's behalf. ## Step 4: Construct the Chain Batches Now we define the specific on-chain actions. Each object in the `chainBatches` array represents a set of calls to be executed on a specific blockchain, in order. Import the hashing helpers from `@spicenet-io/spiceflow-core`. Do not hand-roll them, and do not add a `recentBlock` field: the current chain-batch shape is `{ chainId, calls }`, and the request schema rejects unknown keys. ```typescript theme={null} import { hashChainBatches } from "@spicenet-io/spiceflow-core"; const chainBatches = hashChainBatches([ // Step 0: Deposit Sepolia ETH to escrow { chainId: sepolia.id, calls: [{ to: escrowAddress, data: "0x", value: depositAmount }], }, // Step 1: Swap for cBTC on Citrea. The RFQT object from Step 2 is a valid "Call". { chainId: citreaTestnet.id, calls: [crestRfqt], }, ]); ``` `hashChainBatches` returns each batch as `{ hash, chainId, calls }`. ## Step 5: Sign the Intent and Build the Request The user signs the intent hash. `getIntentHash` takes the signature type, a not-before timestamp (`nbf`), an expiry (`exp`), and the hashed chain batches: ```typescript theme={null} import { getIntentHash } from "@spicenet-io/spiceflow-core"; const nbf = 0n; const exp = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour const digest = getIntentHash("ecdsa", nbf, exp, chainBatches); const signature = await user.signMessage({ message: { raw: digest } }); ``` Assemble the `POST /actions` request body. `chainAuthorizations` are the EIP-7702 authorizations from Step 3, each `{ r, s, yParity, address, chainId, nonce }` (drop the legacy `v` field). Each chain batch also carries its `tokenTransfers` (how the solver moves tokens): ```typescript theme={null} const apiRequest = { user: user.address, chainAuthorizations: [userAuthSepolia, userAuthCitrea], intents: [ { mode: "7702", signatureType: "ecdsa", signature, nbf: Number(nbf), exp: Number(exp), chainBatches: chainBatches.map((b) => ({ ...b, tokenTransfers: [] })), }, ], }; ``` Send it to `POST /actions`. See the [POST /actions](/api-reference/endpoint/actions-create) reference for the full request and response schema. ```bash theme={null} curl -X POST https://tx-submission-api.spicenet.io/actions \ -H "Content-Type: application/json" \ -d '{ "user": "0x...", "chainAuthorizations": [ { "r": "0x...", "s": "0x...", "yParity": 0, "address": "0x...", "chainId": 11155111, "nonce": 0 } ], "intents": [ { "mode": "7702", "signatureType": "ecdsa", "signature": "0x...", "nbf": 0, "exp": 1750000000, "chainBatches": [ { "hash": "0x...", "chainId": 11155111, "tokenTransfers": [], "calls": [ ... ] } ] } ] }' ``` ## What Happens Next: The Solver After you submit the intent, the API makes it available to a network of solvers. A solver will: 1. Pick up your intent. 2. Execute **Step 0** by sending a transaction to the `Delegate` contract on Sepolia, using your signed authorization. This deposits your funds to escrow. 3. Wait for the required block confirmations. 4. Execute **Step 1** by sending a transaction to the `Delegate` contract on Citrea, which performs the swap. 5. As part of the final transaction, the solver also transfers the principal deposit amount (`tokenAmount`) back to your address (`address`) on the destination chain (Citrea). Your application can track each step using the `GET /actions/{actionId}/intents/{intentIndex}/steps/{stepIndex}` endpoint. You have successfully orchestrated a cross-chain action without requiring the user to switch networks or manage gas on the destination chain. # How It Works Source: https://spiceflow-docs.spicenet.io/how-it-works Understanding the technical flow behind Spice Flow API ## Naming Convention The API uses the following naming convention: * **Intent**: The parent action which contains ChainBatch(es) * **ChainBatch**: For a specific chain, and has many Call(s) * **Call**: Has `to`, `data`, and `value` parameters ![Types](https://mintcdn.com/ter-e1ca54a7/bSxPS04-n9iQl8aq/assets/types.png?fit=max\&auto=format\&n=bSxPS04-n9iQl8aq\&q=85\&s=988827aea1b451031829d3a8fbf89dc5) ## Process Flow ![How It Works Flow Diagram](https://mintcdn.com/ter-e1ca54a7/T-BZIBFN376rp_1N/assets/how-it-works.png?fit=max\&auto=format\&n=T-BZIBFN376rp_1N\&q=85\&s=508688a15f70fa640eac06caa7acd860) For a detailed technical explanation of the underlying technologies, including EIP-7702 and the `Delegate` contract, see our deep dive on [EIP-7702](/deep-dive/eip7702) and [Delegate Contract](/deep-dive/delegate). ## What Actually Happens ### 1. Action Submission When you call `POST /actions`: 1. **Validation**: the API validates the action, its intents, and the `chainAuthorizations` 2. **Storage**: the action and its intents are persisted and assigned a tracking `actionId` 3. **Solver hand-off**: the intents are made available to the solver network 4. **Response**: returns the `actionId` for tracking ### 2. Step Execution Each step runs at `POST /actions/{actionId}/intents/{intentIndex}/steps/{stepIndex}`: 1. **Step lookup**: retrieves the step by action id, intent index, and step index 2. **Execution**: performs the work for that step (checks, transactions) 3. **Result capture**: records results and any transaction hashes 4. **Status**: track progress with `GET /actions/{actionId}/intents/{intentIndex}/steps/{stepIndex}` ## Technical Implementation ### Intent Structure ```typescript theme={null} interface Intent { intentId: string; // Signature of chainBatches chainBatches: ChainBatch[]; status: "created" | "executing" | "success" | "error" | "reverted"; steps: IntentStep[]; } ``` ### Step Execution Model ```typescript theme={null} interface IntentStep { stepId: number; // Index [0, n) in chainBatches array chainId: number; // Target chain for this step status: "created" | "executing" | "success" | "error" | "reverted"; transactionHash?: string; // Hash if transaction was sent executionResult?: object; // Results of step execution } ``` ### Transaction Submit Request Structure ```typescript theme={null} interface TransactionSubmitRequest { authorization: [ { chainId: number; address: string; nonce: number; r: string; // ECDSA signature R value s: string; // ECDSA signature S value yParity: number; // ECDSA signature Y parity }, ]; intentAuthorization: { signature: string; // This becomes the intent ID chainBatches: ChainBatch[]; }; } ``` ## Key Concepts ### Intent ID Generation * The intent ID is the **signature of the chainBatches** * This approach is similar to Solana's transaction ID system * Provides a unique, deterministic identifier for each intent ### Step Index System * Steps are indexed as integers: `[0, n)` where n = chainBatches.length * Step 0 = first chain authorization, Step 1 = second, etc. * Steps must be executed sequentially in order ### Authorization Array * **Multiple Authorizations**: Can submit multiple EIP-7702 authorizations in a single transaction * **Cross-Chain Support**: Each authorization can be for different chains or addresses * **Flexible Delegation**: Supports complex multi-chain execution patterns ### Execution Model * **Active Execution**: Calling the POST endpoint triggers actual work * **Unit of Work**: Each step call performs one discrete operation * **Stateful**: Execution state is persisted in the database * **Resumable**: Failed or interrupted intents can be resumed ## Error Handling The API implements comprehensive error handling: * **400 Bad Request**: Invalid transaction format, malformed parameters, or out-of-range step IDs * **404 Not Found**: Intent not found or step index exceeds available steps * **500 Internal Server Error**: Database errors, chain communication failures, or execution errors ```typescript theme={null} // Example error response { "success": false, "error": { "message": "Step execution failed: insufficient gas" } } ``` ## Data Persistence Unlike the current in-memory implementation: * **Database Storage**: All intent data is persisted to database * **Reliable Execution**: Steps can be retried if they fail * **Audit Trail**: Complete execution history is maintained * **Recovery**: System can recover from restarts without losing state ## Monitoring Comprehensive monitoring capabilities: * **Database Logging**: All operations are logged to database * **Execution Tracking**: Detailed step-by-step execution results * **Transaction Hashes**: All on-chain transactions are tracked * **Status Endpoints**: Real-time status via API calls ## Use Cases ### Perp DEXes Power bridge-free native deposits from every network into margin accounts on Perp DEXes. ### Money Markets Enable users to natively deposit assets directly into lending markets from every major network. ### AMMs Facilitate bridge-free provision of liquidity from every network by enabling users to deposit assets into liquidity pools from any chain. # Spice Flow Source: https://spiceflow-docs.spicenet.io/index A single SDK that lets your application work across every major EVM chain. Users deposit with any asset on any chain and your app executes on the destination chain instantly. > [!NOTE] > > If you wish to integrate, please message our CEO [here](https://t.me/PeprikaInferno) ## Quick Start ### Installation ```bash theme={null} bun add @spicenet-io/spiceflow-ui@4.19.0 ``` ### Basic Setup Mount the provider stack (wagmi, react-query, Privy, then `SpiceFlowProvider` — see [Quick Start](/quickstart#step-2-setup-providers)) and add `SpiceDeposit` to let users deposit assets from any chain: ```tsx theme={null} import { SpiceDeposit } from "@spicenet-io/spiceflow-ui"; // Rendered inside the provider stack function DepositButton() { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(false)} depositBatches={yourBatches} styles={{ primaryColor: "#f97316" }} /> ); } ``` ## Core Components > **[SpiceFlowProvider](/sdk/components#spiceflowprovider)** > > Root provider that manages wallet connections, chains, and branding > **[SpiceDeposit](/sdk/components#spicedeposit)** > > Full deposit flow with token selection, wallet connection, and execution > **[SpiceWithdraw](/sdk/components#spicewithdraw)** > > Withdraw assets from Spice balance back to wallet > **[AccountDisplay](/sdk/components#accountdisplay)** > > Account overview popover with balance breakdown ## SDK Documentation > **[Installation and Setup](/sdk/installation)** > > Get started with installation and configuration > **[Components](/sdk/components)** > > Complete component reference and props > **[Hooks](/sdk/hooks)** > > React hooks for building custom UIs > **[Styling](/sdk/styling)** > > Customize components to match your brand ## Examples and Guides > **[Quick Start](/quickstart)** > > Build your first Spice Flow app in 5 minutes > **[Native Swap Example](/guides/native-swap)** > > Complete code walkthrough for cross chain swaps > **[Best Practices](/guides/best-practices)** > > Security guidelines and integration patterns > **[Configuration](/sdk/configuration)** > > Configure chains, providers, and execution modes ## Technical Deep Dive > **[How It Works](/how-it-works)** > > Understand the architecture behind Spice Flow > **[EIP 7702](/deep-dive/eip7702)** > > Learn about EIP 7702 delegation > **[API Reference](/api-reference)** > > REST API documentation for advanced use cases > **[Delegate Contract](/deep-dive/delegate)** > > Deep dive into the on chain delegation contract ## Key Features > **[Go Universal](/guides/native-swap)** > > Make your application feel native to users across every major EVM compatible blockchain ecosystem. > **[Reduced Distribution Costs](/quickstart)** > > Access users and volume from every major blockchain ecosystem without the need to redeploy smart contracts. > **[Native, Bridge Free UX](/frontend-integration/reactjs)** > > Users deposit with existing wallets and assets on their preferred networks. No bridging, no gas management. > **[Easy to Integrate](/quickstart)** > > Client side integration only. No smart contract changes needed. Drop in components and you're live. ## Need Help? > **[Join our Community](https://discord.gg/spicenetio)** > > Get help from our community and team on Discord. > **[Message our CEO](https://t.me/PeprikaInferno)** > > Connect with our CEO to get your questions answered and request a personalized, white glove onboarding to Spice Flow. # Quick Start Source: https://spiceflow-docs.spicenet.io/quickstart Add a protocol action with SDK-owned funding and execution ## Overview This guide walks you through adding Spice Flow to your app. By the end, your users will be able to fund protocol actions across supported chains without manually managing bridges. This guide targets stable SDK 4.19.0. **What you'll build:** * A wallet connection flow (embedded + external wallet) * A protocol deposit interface with SDK-supported tokens and routes * Cross chain execution powered by EIP 7702 *** ## Step 1: Installation Install the Spice Flow UI SDK and Privy for wallet management: ```bash theme={null} bun add @spicenet-io/spiceflow-ui@4.19.0 @spicenet-io/spiceflow-core@0.8.2 "@privy-io/react-auth@>=2.24.0 <4.0.0" react@18 react-dom@18 viem wagmi @tanstack/react-query ``` Reuse compatible peers already in your application. See [Installation](/sdk/installation) for the published peer ranges. *** ## Step 2: Setup Providers `SpiceFlowProvider` reads wallet state from Privy, wagmi, and react-query — your app mounts all four providers, in this order: ```tsx theme={null} "use client"; // For Next.js App Router import type { ReactNode } from "react"; import { WagmiProvider, createConfig, http } from "wagmi"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { PrivyProvider } from "@privy-io/react-auth"; import { SpiceFlowProvider } from "@spicenet-io/spiceflow-ui"; import { getMainnetChains } from "@spicenet-io/spiceflow-core"; import { defineChain, type Chain } from "viem"; // Every chain the installed SDK supports; new chains arrive with SDK upgrades. const chains = getMainnetChains().map((c): Chain => defineChain({ id: c.id, name: c.displayName, nativeCurrency: c.nativeCurrency, rpcUrls: { default: { http: [c.rpcUrl] } }, blockExplorers: { default: { name: "Explorer", url: c.blockExplorer } }, }), ) as [Chain, ...Chain[]]; const wagmiConfig = createConfig({ chains, transports: Object.fromEntries(chains.map((c) => [c.id, http()])), }); const queryClient = new QueryClient(); function App({ children }: { children: ReactNode }) { return ( {/* supportedChainIds omitted: SpiceFlowProvider defaults to every supported chain */} {children} ); } export default App; ``` > [!TIP] > > Get your Privy App ID from [privy.io](https://privy.io) — it goes on `PrivyProvider`. This setup > enables every chain the SDK supports; to restrict, pass explicit chains to wagmi, Privy, and > `supportedChainIds`, keeping the three in sync. See [Configuration](/sdk/configuration). *** ## Step 3: Add a Protocol Action Use `SpiceSupply` for a vault deposit, supply or payment. Define destination calls once; the SDK owns source selection, routing, fees and execution. This ERC-4626 example accepts your vault address as a prop; the vault must use Base USDC as its underlying asset. ```tsx theme={null} import { useState } from "react"; import { encodeFunctionData, erc20Abi, parseAbi, type Address } from "viem"; import { SpiceSupply } from "@spicenet-io/spiceflow-ui"; const usdc = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Address; const vaultAbi = parseAbi([ "function deposit(uint256 assets, address receiver) returns (uint256 shares)", ]); export function SupplyButton({ vault }: { vault: Address }) { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(false)} actionLabel="Supply" destinationToken={{ chainId: 8453, address: usdc }} buildActionCalls={(amount, ctx) => [ { to: usdc, value: 0n, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, amount], }) }, { to: vault, value: 0n, data: encodeFunctionData({ abi: vaultAbi, functionName: "deposit", args: [amount, ctx.beneficiary ?? ctx.userAddress], }) }, ]} /> ); } ``` `amount` is a raw bigint in destination-token units. The SDK resolves the token's symbol and decimals. The default `amountDirection="input"` adds fees to the entered principal; choose `"input-inclusive"` for a total spending budget or `"output"` with `paymentAmount` for a fixed payment. ## Step 4: Add Account Controls (Optional) ```tsx theme={null} import { SpiceAccount } from "@spicenet-io/spiceflow-ui"; export function AccountMenu() { return ; } ``` This composes the account display and its deposit/withdraw modals with no required component props. `SpiceDeposit` adds funds to Spice balance; `SpiceWithdraw` takes Spice balance back to a wallet. They are distinct from depositing into, or withdrawing from, your protocol's position. Keep `AccountDisplay` if your app already owns those modals. For your own presentation, use the same action controller through [Custom UI Integration](/guides/custom-ui). Existing integrations should read [Migration to 4.19.0](/sdk/migration). *** ## Environment Setup Create a `.env.local` file in your project root: ```env theme={null} NEXT_PUBLIC_PRIVY_APP_ID=your_privy_app_id_here ``` Get your credentials: * **Privy:** Sign up at [privy.io](https://privy.io) and create an app *** ## How It Works 1. The user connects their wallet and selects a source asset and amount. 2. The SDK quotes funding, any swaps and fees; the UI enables submission when the plan is ready. 3. A matching-token external action on the destination chain executes directly from that wallet. Routed external sources fund the embedded wallet, then the SDK coordinates cross-chain delivery and destination calls. 4. Supply verifies the destination transaction receipt and awaits application work in `onSuccess` if provided. Backend settlement may continue after destination execution. Source transfers and direct wallet transactions can require native gas. Routed execution fees are shown in the SDK quote. Your app defines the protocol calls and intended beneficiary, while the SDK handles the funding and execution path. *** ## What's Next? > **[Components](/sdk/components)** > > Explore all available components and their props > **[Hooks](/sdk/hooks)** > > Build custom UIs with React hooks > **[Styling](/sdk/styling)** > > Customize the appearance to match your brand > **[Configuration](/sdk/configuration)** > > Configure chains, providers, and execution modes *** ## Troubleshooting ### "Window is not defined" Error If you see this in Next.js, ensure you're using the `"use client"` directive: ```tsx theme={null} "use client"; import { SpiceFlowProvider } from "@spicenet-io/spiceflow-ui"; ``` ### "usePrivy must be used within PrivyProvider" (or a wagmi context error) The provider stack from Step 2 isn't mounted above the component. `SpiceFlowProvider` alone is not enough — `WagmiProvider`, `QueryClientProvider`, and `PrivyProvider` must wrap it. ### Wallet Not Connecting 1. Check that your provider credentials are correct in `.env.local` 2. Check the browser console for any error messages ### Need More Help? > **[Join our Discord](https://discord.gg/spicenetio)** > > Get help from the community and our team # Components Source: https://spiceflow-docs.spicenet.io/sdk/components Complete reference for all Spice Flow SDK components > [!NOTE] > > This reference is verified against the published `@spicenet-io/spiceflow-ui` package by > `tools/check-sdk-drift.mjs` on every change. The check verifies export and prop names; examples and behavioral guidance target stable SDK 4.19.0. ## SpiceFlowProvider The root provider for chain configuration, theming, and execution mode. It reads wallet state from Privy, wagmi, and react-query — mount those above it in your app (see [Quick Start Step 2](/quickstart#step-2-setup-providers)). ### Usage ```tsx theme={null} import { SpiceFlowProvider } from "@spicenet-io/spiceflow-ui"; {children} ``` ### Props | Prop | Type | Required | Default | Description | | - | - | - | - | - | | `provider` | `"privy"` | No | `"privy"` | Wallet provider. Privy is the only provider in the current release | | `children` | `ReactNode` | Yes | | Your application components | | `network` | `"mainnet" \| "testnet"` | No | `"testnet"` | Network environment. **Set this explicitly**: leaving it default sends submissions to testnet | | `nativeChainId` | `number` | No | | Your app's chain. Deposits are delivered here unless a component sets `destinationChainId`, and External-mode deposits ask the wallet to be on it | | `theme` | `SpiceTheme` | No | | Branding and theme config (see [Styling](/sdk/styling)) | | `privyAppId` | `string` | No | | No effect — set `appId` on your own `PrivyProvider` | | `supportedChainIds` | `number[]` | No | Per network | Override supported chain IDs | | `allowedTokens` | `string[]` | No | | Restrict the token list by lowercase symbol, e.g. `["usdc", "eth"]` | | `mode` | `"7702" \| "presign" \| "ondemand"` | No | `"7702"` | Execution mode. `"presign"` behaves exactly like `"ondemand"` | | `whitelist` | `SpiceFlowWhitelistOptions \| false` | No | | Gate the flow to whitelisted wallets. When a user is not whitelisted the SDK forces `ondemand` mode and blanks the allowed tokens | | `rpcOverrides` | `Record` | No | | Per-chain RPC URL overrides | | `skipFlow` | `number[]` | No | `[]` | No effect | | `appName` | `string` | No | | No effect. Set `theme.appName` instead | | `apiUrl` | `string` | No | | Override the relayer API URL | | `embeddedWalletConfig` | `{ createOnLogin?: "off" \| "users-without-wallets" \| "all-users" }` | No | | No effect — set `embeddedWallets` in the `PrivyProvider` config | | `assetDisplay` | `AssetDisplayOverrides` | No | | Display-only receipt/token metadata overrides | | `swapper` | `{ enabled?: boolean; integratorId?: string }` | No | off | Card and exchange deposit sources in `SpiceDeposit`, opt-in. See [Card and Exchange Deposits](/sdk/configuration#card-and-exchange-deposits) | ### Examples ```tsx Privy (Mainnet) theme={null} {children} ``` ```tsx Ondemand Mode theme={null} {children} ``` *** ## SpiceDeposit The main deposit flow. Handles everything: Privy login, external wallet connection, token selection, escrow deposit, 7702 signing, and solver execution. Use `SpiceSupply` for a protocol action; this component handles account deposits. ### Usage ```tsx theme={null} import { SpiceDeposit } from "@spicenet-io/spiceflow-ui"; setIsOpen(false)} depositBatches={batches} styles={{ primaryColor: "#f97316" }} /> ``` ### Props | Prop | Type | Required | Default | Description | | - | - | - | - | - | | `isOpen` | `boolean` | Yes | | Control modal visibility | | `onClose` | `() => void` | Yes | | Close handler | | `styles` | `CustomStyles` | No | | Custom styling | | `depositBatches` | `ChainBatch[] \| ((ctx: PostDepositContext) => Promise) \| ((amount, token) => Promise)` | No | | Destination chain calls to execute after deposit | | `onDepositAmountChange` | `(amount: string) => void` | No | | Fires when the user changes the deposit amount | | `destinationChainId` | `number` | No | | Target chain for execution | | `destinationTokenAddress` | `string` | No | | Target token on the destination chain | | `onDepositExecute` | `(amount: string) => Promise` | No | | Callback for non-7702 mode (app handles execution) | | `depositActionLabel` | `string` | No | | Custom label for the action button | | `sponsorGas` | `boolean` | No | `false` | Backend sponsors gas | | `skipTokenSelection` | `boolean` | No | `false` | Skip the token selection step | | `directSubmit` | `boolean` | No | `false` | Submit from the deposit form without the extra confirmation modal; validation and wallet prompts remain | | `onSubmit` | `() => void` | No | | Submission notification | | `skipRecovery` | `boolean` | No | `false` | Skip the stranded-funds recovery prompt | | `escrowRecovery` | `EscrowRecovery` | No | | Recover funds stuck in escrow | | `onDepositSuccess` | `(detail) => void` | No | | Success callback. `detail.type` is `"deposit"` for an escrow deposit, or `"wallet-funding"` when a card or exchange deposit landed in an external wallet | | `onDepositError` | `(error) => void` | No | | Error callback | > [!NOTE] > > To restrict which tokens appear, set `allowedTokens` on `SpiceFlowProvider` (lowercase symbols), not on > `SpiceDeposit`. > [!NOTE] > > With `swapper={{ enabled: true }}` on `SpiceFlowProvider`, the modal opens with a Wallet / Card / Exchange > chooser so users can fund by debit card or from an exchange. Off by default. See > [Card and Exchange Deposits](/sdk/configuration#card-and-exchange-deposits) for where the funds land in each > wallet mode. ### depositBatches The `depositBatches` prop defines what happens on the destination chain after the deposit. It can be a static array or an async function that returns batches based on the deposit amount: ```tsx theme={null} // Static batches const batches = [ { chainId: 8453, calls: [ { to: TOKEN, value: 0n, data: encodeApprove(VAULT, amount) }, { to: VAULT, value: 0n, data: encodeDeposit(TOKEN, amount) }, ], }, ]; // Dynamic batches (computed from deposit amount) const dynamicBatches = async (amount: string, tokenAddress: string) => { const parsedAmount = parseUnits(amount, 6); return [ { chainId: 8453, calls: [ { to: tokenAddress, value: 0n, data: encodeApprove(VAULT, parsedAmount) }, { to: VAULT, value: 0n, data: encodeDeposit(tokenAddress, parsedAmount) }, ], }, ]; }; ``` *** ## SpiceSupply Runs a deposit / supply / stake action end to end: token selection, funding, fee quoting, and execution, driven by a `buildActionCalls` callback that returns your destination-chain calls. This is the stock presentation of `useSpiceSupply`. Keep your existing UI with that controller when a stock modal does not fit. See [Custom UI Integration](/guides/custom-ui). ### Usage ```tsx theme={null} import { SpiceSupply, type BuildActionCallsContext, type Call } from "@spicenet-io/spiceflow-ui"; const buildActionCalls = (amount: bigint, ctx: BuildActionCallsContext): Call[] => [ { to: ctx.destinationToken, value: 0n, data: encodeApprove(PROTOCOL, amount) }, { to: PROTOCOL, value: 0n, data: encodeSupply(ctx.destinationToken, amount, ctx.beneficiary ?? ctx.userAddress) }, ]; setIsOpen(false)} actionLabel="Supply" destinationToken={{ chainId: 8453, address: TOKEN }} buildActionCalls={buildActionCalls} styles={{ primaryColor: "#f97316" }} /> ``` > [!NOTE] > > `buildActionCalls` takes `(amount, ctx)` in that order. `ctx` (`BuildActionCallsContext`) carries > `userAddress`, `executorAddress`, `beneficiary`, `chainId`, `destinationToken`, and fee fields. > The bigint is in destination-token units; honor it after swaps and fee allocation. Read the current prop and context > shapes from the installed package (`SpiceSupplyProps`, `BuildActionCallsContext`). Routes paid from the user's Spice balance need a Spicenet funding batch in front of the destination batch. The SDK builds it automatically. `buildSpicenetBatch` and `feeExecutionMode` are deprecated compatibility props; leave them unset in new integrations. Existing values remain supported. ### Props | Prop | Type | Required | Default | Description | | - | - | - | - | - | | `isOpen` | `boolean` | Yes | | Modal visibility | | `onClose` | `() => void` | Yes | | Close handler | | `actionLabel` | `string` | Yes | | Submit button label | | `destinationToken` | `DestinationTokenDeclaration` | Yes | | Chain/address; metadata resolves automatically | | `buildActionCalls` | `(amount, ctx) => Call[] \| Promise` | Yes | | Destination contract calls | | `amountDirection` | `"input" \| "input-inclusive" \| "output"` | No | `"input"` | Principal plus fees, total budget, or fixed destination amount | | `paymentAmount` | `string` | For output mode | | Fixed destination amount | | `recipient` | `Address` | No | | Intended protocol beneficiary | | `sourceChains` | `number[]` | No | Provider chains | Restrict source chains | | `supportedSourceAssets` | `string[]` | No | | Restrict by symbol, address or `native` | | `onSubmit` | `() => void` | No | | Accepted submission before prompts | | `onSuccess` | `(hash: string) => void \| Promise` | No | | Awaited application updates | | `onError` | `(error, detail?) => void` | No | | Error handler | | `combineSourceBalances` | `boolean` | No | `false` | Combine matching eligible wallet and escrow balances | | `fundingSources` | `SupplyFundingSources` | No | | Token and nominated account positions for an input-inclusive purchase; see [Cash funding](/guides/cash-funding) | | `beforeSubmit` | `(review: SupplySubmissionReview) => void \| Promise` | No | | Awaited host check before execution | | `submitDisabled` | `boolean` | No | `false` | Host readiness restriction | | `onStateChange` | `(state: SupplyModalState) => void` | No | | Observe readiness, fees, finalization and backend recovery status | | `feeOnlyAction` | `boolean` | No | `false` | Existing principal; SDK funds only execution fees | | `styles` | `CustomStyles` | No | Provider theme | Presentation overrides | `onPayExecute` blocks execution until migrated. Define the calls with `buildActionCalls` and use `onSuccess` for app updates. See [Migration](/sdk/migration) for direct versus routed execution. *** ## SpiceWithdraw Withdraw assets from the user's Spice balance back to their wallet. ### Usage ```tsx theme={null} import { SpiceWithdraw } from "@spicenet-io/spiceflow-ui"; setIsOpen(false)} styles={{ primaryColor: "#f97316" }} /> ``` ### Props | Prop | Type | Required | Default | Description | | - | - | - | - | - | | `isOpen` | `boolean` | Yes | | Control modal visibility | | `onClose` | `() => void` | Yes | | Close handler | | `styles` | `CustomStyles` | No | | Custom styling | | `onWithdrawExecute` | `(amount: string) => Promise` | No | | Callback for non-7702 mode | | `onWithdrawSuccess` | `(data: { token, amount, chainId }) => void` | No | | Success callback | | `onWithdrawError` | `(error: string, detail?) => void` | No | | Error callback | | `availableTokens` | `Record` | No | | Restrict withdrawable tokens, keyed by chain ID | | `buildSpicenetBatch` | `(params) => ChainBatch \| null` | No | `buildSpicenetWithdrawalBatch` | Override the Spicenet-side delivery batch | *** ## SpiceLock Lock tokens for a duration (veToken style), using the shared input-inclusive Supply controller. The amount and duration inputs are driven by hooks you create with `useAssetInput` and `useLockDuration` and pass in, so you can read the live selection for your own preview UI. ### Usage ```tsx theme={null} import { SpiceLock } from "@spicenet-io/spiceflow-ui"; setIsOpen(false)} lockBatches={lockBatches} lockTokenSymbol="TOKEN" destinationTokenAddress={TOKEN} lockChainId={4114} durationOptions={[ { label: "1 Month", months: 1 }, { label: "3 Months", months: 3 }, { label: "6 Months", months: 6 }, { label: "12 Months", months: 12 }, ]} styles={{ primaryColor: "#f97316" }} /> ``` ### Props | Prop | Type | Required | Default | Description | | - | - | - | - | - | | `isOpen` | `boolean` | Yes | | Modal visibility | | `onClose` | `() => void` | Yes | | Close handler | | `lockBatches` | `ChainBatch[] \| ((ctx: LockBatchContext) => ChainBatch[])` | Yes | | Destination-only calls using fee-adjusted `amountWei` | | `lockTokenSymbol` | `string` | Yes | | Display symbol for the lock token | | `lockChainId` | `number` | Yes | | Chain where the lock contract lives | | `durationOptions` | `LockDurationOption[]` | Yes | | Available lock durations | | `styles` | `CustomStyles` | No | | Custom styling | | `dark` | `boolean` | No | | Dark mode override | | `lockTokenLogoURI` | `string` | No | | Logo URL for the lock token | | `votingPowerEstimate` | `(amount, months) => number` | No | | Estimate voting power from amount and duration | | `sourceToDestinationConversionRate` | `number` | No | | Conversion rate display | | `onLockSuccess` | `(txHash?: string) => void \| Promise` | No | | Awaited application updates | | `beneficiary` | `Address` | No | | Intended position owner | | `onSubmit` | `() => void` | No | | Accepted submission before prompts | | `onAddFunds` | `() => void` | No | | Handler for "add funds" | | `destinationTokenAddress` | `Address` | No | | Target token on the lock chain | > [!NOTE] > > `SpiceLock` also accepts hoistable input hooks (`sourceAssetInputHook`, `lockDurationHook`) and > `escrowAddress`. The builder receives destination-token `amountWei`, `beneficiary` and `executorAddress`; > the SDK supplies conversion and delivery. `onLockExecute` blocks execution until moved into the > builder and success callback. See `SpiceLockModalProps` for the full shape. *** ## SpiceAccount Composes `AccountDisplay`, `SpiceDeposit` and `SpiceWithdraw`, including open/close state and the selected withdrawal asset. It has **zero required props** inside the provider stack. ```tsx theme={null} import { SpiceAccount } from "@spicenet-io/spiceflow-ui"; ``` Optional `depositProps` and `withdrawProps` customize the nested flows without their visibility props. It also accepts the display's branding, extra assets, token prices and mode-toggle props, but owns `onDepositClick` / `onWithdrawClick` itself. Keep `AccountDisplay` if your app needs to own those modals. *** ## AccountDisplay A popover for wallet and Spice balances and deposit/withdraw actions. It uses shared inventory and policy. `extraAssets` can add metadata or undiscovered positions but cannot override an SDK-acquired balance, including zero. Host `tokenPrices` are display-only fallbacks, not execution prices. ### Usage ```tsx theme={null} import { AccountDisplay } from "@spicenet-io/spiceflow-ui"; setDepositOpen(true)} onWithdrawClick={() => setWithdrawOpen(true)} /> ``` ### Props | Prop | Type | Required | Default | Description | | - | - | - | - | - | | `styles` | `CustomStyles` | No | | Custom styling | | `dark` | `boolean` | No | | Dark mode override | | `appName` | `string` | No | `"Spice"` | Display name | | `status` | `"active" \| "inactive"` | No | `"active"` | Account status indicator | | `statusLabel` | `string` | No | | Custom status text | | `onDepositClick` | `() => void` | No | | Deposit button handler | | `onWithdrawClick` | `() => void` | No | | Withdraw button handler | | `onDepositSuccess` | `() => void` | No | | Called after a successful deposit | | `customSections` | `CustomSection[]` | No | | Add custom sections to the popover | | `extraAssets` | `SpiceAsset[]` | No | | Additional assets to display (e.g. the app's own positions) | | `tokenPrices` | `Record` | No | | Display prices for tokens without an SDK price, keyed by `chainId:lowercaseAddress` | | `showModeToggle` | `boolean` | No | | Show the Embedded / External mode toggle | | `defaultMode` | `"7702" \| "non7702"` | No | | Initial mode for the toggle | *** ## SelectChainModal The source-chain picker on its own, for when you want the chain-selection step outside the full deposit flow. See `SelectChainModal` in the installed package for its current props. ```tsx theme={null} import { SelectChainModal } from "@spicenet-io/spiceflow-ui"; ``` *** ## ProviderLogin Wallet connection button for the configured provider (Privy). ### Usage ```tsx theme={null} import { ProviderLogin } from "@spicenet-io/spiceflow-ui"; console.log("Logged in")} /> ``` ### Props | Prop | Type | Required | Description | | - | - | - | - | | `onAuthSuccess` | `() => void` | No | Callback after successful authentication | | `autoTrigger` | `boolean` | No | Automatically trigger login on mount | *** ## Next Steps > **[Hooks](/sdk/hooks)** > > Build custom UIs with React hooks > **[Styling](/sdk/styling)** > > Customize component appearance > **[Configuration](/sdk/configuration)** > > Configure chains and providers > **[Examples](/guides/native-swap)** > > See complete examples # Configuration Source: https://spiceflow-docs.spicenet.io/sdk/configuration Configure wallet providers, supported chains, and SDK behavior ## Wallet Provider Spice Flow uses Privy for wallet connections and embedded wallets. ### Privy Best for embedded wallets and social logins. ```tsx theme={null} {children} ``` **Configuration:** your Privy app ID and embedded-wallet policy are configured on your own `PrivyProvider` (which the SDK reads through Privy's hooks), not on `SpiceFlowProvider`: ```tsx theme={null} ``` The `privyAppId` and `embeddedWalletConfig` props on `SpiceFlowProvider` exist for compatibility but have no effect in the current release. **Get Your Privy App ID:** 1. Sign up at [privy.io](https://privy.io) 2. Create a new app 3. Copy your App ID from the dashboard *** ## Execution Modes `mode="7702"` selects the embedded account (default). `mode="ondemand"` selects the external account; `"presign"` is an alias of `"ondemand"`. These account modes do not mean that all external-mode actions are implemented through host callbacks. ```tsx theme={null} {children} ``` For Supply/Pay/Lock, the SDK owns execution in both modes: * A positive-amount external action using its matching destination token on the destination chain executes directly from that wallet, with its native gas. Fee-only and proceeds-funded actions are excluded. * By default, other supported sources use the routed path through the embedded wallet. The host defines destination calls and any separate beneficiary; it does not send the transaction through `onPayExecute` or `onLockExecute`. For the supported routed external-caller option, see [Wallets and callers](/sdk/migration#wallets-and-callers). * Same-wallet, same-chain embedded actions that need no routing execute without an unnecessary Spice funding leg. `SpiceDeposit` / `SpiceWithdraw` retain their own non-7702 callbacks (`onDepositExecute`, `onWithdrawExecute`). They are account funding/withdrawal flows, not the Supply/Lock override contract. Use `useSpiceAccount()` to render mode and owner. Omit a fixed provider `mode` when the SDK account toggle should control it. Funding from both connected wallets is enabled by default; mode chooses the account owner and is not a source-wallet filter. See [Migration](/sdk/migration). *** ## Card and Exchange Deposits Available from `@spicenet-io/spiceflow-ui@4.7.11`. When enabled, `SpiceDeposit` opens with a source chooser in front of the existing flow: **Wallet**, **Card**, or **Exchange**. Card and exchange deposits run through the Swapper Finance widget, and the SDK verifies on chain that the USDC arrived before handing off. Users can fund with a debit card or from an exchange account instead of only from a connected wallet. It is **off by default**. Nothing changes for your integration until you opt in: ```tsx theme={null} {children} ``` `enabled: true` is the only required field. The integrator id defaults to Spicenet's; pass `integratorId` only if Swapper has issued you your own. ### Where the funds land Swapper delivers USDC to the wallet the user is operating with, and the wallet mode decides what happens next. | Wallet mode | Delivered to | Chain | Then | | - | - | - | - | | Embedded (`7702`) | The user's embedded wallet | Arbitrum | Continues into the escrow deposit and credits the spice balance, which is usable on any supported chain | | External (`presign` / `ondemand`) | The user's connected wallet | Your app's chain if it is Base or Arbitrum, otherwise Arbitrum | The flow ends. Funds are in the user's wallet and no spice balance is credited | Swapper currently delivers to **Base** and **Arbitrum**. An embedded deposit always lands on Arbitrum because escrow abstracts the chain downstream. An external deposit lands on your app's chain when Swapper covers it, and on Arbitrum when it does not. The app's chain is read from `destinationChainId` on `SpiceDeposit`, falling back to `nativeChainId` on the provider. > [!NOTE] > > If your app runs on a chain Swapper does not deliver to, an external-wallet user's card or exchange deposit > ends up on Arbitrum, in their own wallet. Consider enabling this only for embedded-wallet users on those > chains, or make sure your flow can handle funds arriving there. ### Telling the two outcomes apart `onDepositSuccess` on `SpiceDeposit` reports which path completed through `detail.type`: ```tsx theme={null} { if (detail.type === "wallet-funding") { // Card/exchange deposit landed in the user's external wallet. No spice credited. return; } // "deposit": escrow deposit completed and spice was credited. }} /> ``` Code that already checks for `"deposit"` keeps working and will not treat a wallet funding as a spice credit. The source chooser only appears on `network="mainnet"`; testnets skip it and go straight to the wallet flow. *** ## Network Configuration Set the network to control which chains are available: ```tsx theme={null} ``` ### Default Chains by Network Use the chain set from your installed package, so configuration follows its supported networks: ```tsx theme={null} import { getChainIdsByNetwork } from "@spicenet-io/spiceflow-ui"; const mainnetChainIds = getChainIdsByNetwork("mainnet"); const testnetChainIds = getChainIdsByNetwork("testnet"); ``` SDK 4.19.0 depends on core 0.8.2. A supported chain still needs the token mappings, liquidity and execution contracts for your requested flow; inclusion in this list does not prove every pair is routable. ### Custom Chain IDs Override the defaults with `supportedChainIds`: ```tsx theme={null} {children} ``` ### RPC provider and token discovery Wallet token discovery and USD prices use the Alchemy API of the RPC endpoint in use for a chain. On an endpoint that is not Alchemy's, or on a chain where Alchemy has no token API, the SDK lists only the tokens configured for that chain, and balances show without provider prices. Pass your own endpoint per chain with `rpcOverrides`; a chain without an entry uses the SDK's built-in endpoint. Production integrations should supply their own endpoints for the chains they exercise. Wagmi transports alone do not configure SDK reads. Rollup price reads can use configured RPCs or the SDK's mapped-asset fallback pricing. Built-in provider endpoints use credential-free proxies; keep private provider keys on your server. *** ## Liquidity Venues Available from `@spicenet-io/spiceflow-ui@4.8.0`. When a flow has to swap on the destination chain, the SDK quotes every liquidity venue that covers that chain and takes the best fill: the most output for an exact input, the least input for an exact output. Routing is decided in the SDK, on your RPC. The solver only executes the calls the winning quote produced. Leave `liquidityVenues` off and you get the built-in venues: | Chain | Built-in venues | | - | - | | Citrea (4114, 5115) | Built-in Citrea routing | | Base (8453) | Uniswap v3, Uniswap v4, Aerodrome Slipstream | | Robinhood (4663) | Uniswap v3, SushiSwap, Uniswap v4 | | Ethereum (1), Arbitrum (42161), Polygon (137), BSC (56), Monad (143) | Uniswap v3 | Every built-in serves both swap directions, so a venue is never reachable for an exact-input swap but missing for an exact-output one. A supported chain still needs a configured venue and liquidity for the requested swap pair. The SDK has no keyless 1inch fallback in its default venue list. For deployments outside the built-ins, declare a venue explicitly; both exact-input and exact-output routing use the declaration. ### Adding your own venue Declare it once on the provider and every component picks it up. This is the whole change needed to route through a deployment the SDK does not ship — here, the SushiSwap v3 deployment on Robinhood: ```tsx theme={null} {children} ``` Your venue races the built-ins on price. It does not override them, and it is used in both swap directions. ### Turning the built-ins off Set `includeDefaults: false` to drop every built-in venue, and route only through what you declared: ```tsx theme={null} liquidityVenues={{ venues: [ /* ... */ ], includeDefaults: false, }} ``` On a chain with no venue at all, quoting then fails naming that chain rather than falling back to something you did not configure. Leave `includeDefaults` off (or `true`) to keep the built-ins and add yours alongside them. ### Venue fields Supply the **deployment's** addresses, not a pool address. A v3-style pool calls back into `msg.sender`, so swaps go through the router and quotes through the quoter; the SDK finds the pools itself from the fee tiers (or tick spacings) and hub currencies. | `protocol` | Required | Optional | | - | - | - | | `uniswap-v3` | `chainId`, `swapRouter`, `quoterV2` | `name`, `feeTiers`, `hubCurrencies`, `routerHasDeadline` | | `uniswap-v4` | `chainId`, `quoter`, `universalRouter`, `permit2` | `name`, `hubCurrencies` | | `aerodrome-slipstream` | `chainId`, `swapRouter`, `quoterV2` | `name`, `tickSpacings`, `hubCurrencies` | Each entry names exactly one chain. Declare the same deployment twice if it runs on two chains. * **`name`** is shown to the user as `via ` once an exact-output swap is quoted. It defaults to the protocol. * **`hubCurrencies`** are the intermediate tokens tried for two-hop routes, usually wrapped native and the chain's main stablecoin. * **`feeTiers`** / **`tickSpacings`** are the pool tiers to search. Include any non-standard tier your venue uses, such as PancakeSwap's 2500. > [!WARNING] > > Set `routerHasDeadline: true` when the deployment forked the **original** Uniswap `SwapRouter`, whose > exact-output structs carry a deadline. SwapRouter02 forks do not, and this is the default. > > Getting it wrong is invisible until execution: quoting goes through the quoter, which does not care, > so the quote looks correct either way and only the transaction reverts. If your exact-output swaps > quote cleanly but revert on chain, this flag is the first thing to check. > [!NOTE] > > Every venue covering a chain is quoted on every keystroke, and each one costs several quoter calls > against **your** RPC (roughly `feeTiers x (1 + hubCurrencies)` per venue). Adding venues improves > fill and increases RPC load. Scope `chainId` tightly and keep `hubCurrencies` to the tokens that > actually carry liquidity. ### Venues from another protocol family Most DEXs are forks of one of the three families above, so adding one is addresses only. A venue built on a different family — a Uniswap v2 pair, Curve, Balancer — needs an adapter inside the SDK, because the SDK owns the calldata rather than asking you to encode swaps yourself. Send us the chain, router, quoter and one funded pool and it becomes a `protocol` every integration can name. *** ## Theming Set your app's visual identity through the `theme` prop. All SDK components inherit these values. ```tsx theme={null} {children} ``` See [Styling](/sdk/styling) for the full theming reference. *** ## API URL Override By default, the SDK connects to the Spicenet relayer. Override this if you're proxying requests or using a custom deployment: ```tsx theme={null} {children} ``` *** ## Complete Provider Reference | Prop | Type | Default | Description | | - | - | - | - | | `provider` | `"privy"` | `"privy"` | Wallet provider (Privy only in the current release) | | `network` | `"mainnet" \| "testnet"` | `"testnet"` | Network environment | | `nativeChainId` | `number` | | Your app's chain. Deposits are delivered here unless a component sets `destinationChainId`, and External-mode deposits ask the wallet to be on it | | `theme` | `SpiceTheme` | | Colors, dark mode, surfaces, fonts, app name | | `privyAppId` | `string` | | No effect — set `appId` on `PrivyProvider` | | `supportedChainIds` | `number[]` | Per network | Override supported chains | | `allowedTokens` | `string[]` | | Restrict tokens by lowercase symbol | | `mode` | `"7702" \| "presign" \| "ondemand"` | `"7702"` | Execution mode. `"presign"` behaves exactly like `"ondemand"` | | `whitelist` | `SpiceFlowWhitelistOptions \| false` | | Gate the flow to whitelisted wallets | | `rpcOverrides` | `Record` | | Per-chain RPC overrides | | `skipFlow` | `number[]` | `[]` | No effect | | `appName` | `string` | | No effect. Set `theme.appName` instead | | `apiUrl` | `string` | | Override relayer API URL | | `embeddedWalletConfig` | `object` | | No effect — set `embeddedWallets` in the `PrivyProvider` config | | `assetDisplay` | `AssetDisplayOverrides` | | Display metadata overrides; no change to token identity, balances or execution units | | `swapper` | `SpiceSwapperConfig` | off | Enable card and exchange deposits. See [Card and Exchange Deposits](#card-and-exchange-deposits) | | `liquidityVenues` | `LiquidityVenuesConfig` | Built-ins | Declare your own swap venues, or turn the built-ins off. See [Liquidity Venues](#liquidity-venues) | *** ## Next Steps > **[Styling](/sdk/styling)** > > Customize component appearance > **[Components](/sdk/components)** > > Explore available components > **[Examples](/guides/native-swap)** > > See complete examples > **[Best Practices](/guides/best-practices)** > > Security and integration patterns # Hooks Source: https://spiceflow-docs.spicenet.io/sdk/hooks React hooks for building custom UIs with Spice Flow For stable SDK 4.19.0, use the shared controllers below for custom UIs. Stock components consume these same controllers. The lower-level hooks remain available for existing integrations. ## useSpiceSupply Owns the Supply/Pay action from inventory and quote through fees, Max, funding and completion. See the [complete typed example](/guides/custom-ui#complete-example). | Input | Required | Purpose | | - | - | - | | `enabled` | No; defaults to `true` | Lifecycle gate: `true` for an active form, or `isOpen` for a modal that stays mounted | | `destinationToken` | Yes (can be undefined while loading) | Chain/address; optional metadata overrides | | `selectedAsset` | Yes (can be null) | Selected full `Asset` plus source amount string | | `buildActionCalls` | Yes | Destination contract calls using the provided bigint amount/context | | `amountDirection` | No | `input` (default), `input-inclusive`, or `output` | | `paymentAmount` | For output or account funding | Destination amount in output mode; total funding-token budget with `fundingSources` | | `recipient` | No | Intended beneficiary when different from the default | | `onSubmit`, `onSuccess`, `onError` | No | Submission notification, awaited app update, error handling | | `sourceChains`, `supportedSourceAssets` | No | Product restrictions; source assets accept symbols, addresses or `native` | | `feeOnlyAction`, `actionInput`, `actionProceeds`, `feeFunding` | No | Fee funding for actions with existing principal | | `combineSourceBalances` | No; defaults to `false` | Combine matching eligible wallet and escrow positions | | `fundingSources` | No | Account-funded purchase: funding token and selected position IDs; see [Cash funding](/guides/cash-funding) | | `beforeSubmit` | No | Awaited `(review) => void \| Promise` check before execution; throwing rejects submission | | `submitDisabled` | No | Host readiness restriction, combined with SDK readiness | Other optional fields are declared in `UseSpiceSupplyParams`, including source-wallet inclusion, additional tokens and custom swap resolvers. Do not add these just to reproduce the defaults. Keep `enabled` true while quotes and fees load; use `canExecute` for submit-button readiness. Setting `enabled` to true does not submit a transaction. Explicit false disables this controller's preparation and submission, which is useful when a hidden form remains mounted. An always-active form needs three basic inputs: `destinationToken`, `selectedAsset`, and `buildActionCalls`. Unlike Supply, `useSpiceLock` still requires an explicit `enabled` value. | Return field/action | Render or use | | - | - | | `assets.displayAssets` | Eligible source assets; key by `assetPositionId` | | `assets.loading`, `assets.refreshing`, `assets.error` | Initial load, background refresh and inventory errors | | `selectedAsset`, `capability` | Current displayed selection and eligibility | | `quote` | Routing state, output/input estimates and `refreshQuote()` | | `fees.preview`, `fees.ready`, `fees.isLoading`, `fees.error` | Fee quote with its own token symbol/decimals and readiness | | `amount.sourceRequiredAmount` | Raw source debit cap; use selected source decimals | | `amount.availableBalance`, `assets.getAvailableBalance(asset)` | SDK-calculated spendable balance, including eligible combined positions when enabled | | `assets.fundingSources`, `feeQuote` | Account funding candidates and accepted plan; see [Cash funding](/guides/cash-funding) | | `canExecute`, `submit()` | Button readiness and submission for the configured direction | | `max()` | Promise of a fee-aware source amount string, or null when unavailable | | `status` | Step, status text, destination hash, errors, busy state and finalization state | | `status.pending`, `status.recoveryChecking`, `status.recoveryError` | Existing-action reconciliation state; do not bypass `canExecute` | | `status.actionId`, `status.intentId`, `status.autoContinue` | Backend action identity and continuation capability when available | | `status.submissionStatus` | `"unknown"` when submission may have been accepted but its response is unresolved | | `refreshStatus()` | Recheck the existing action; does not submit a new action | | `retryFinalization()` | Retry failed app updates without resending a confirmed transaction | | `retrySupply()` | Clear the failed Supply state and request a fresh quote; does not submit | | `resetAfterSuccess()` | Reset Supply success state and refresh balances | Do not independently estimate fees or rebuild batches around this controller. Preserve token units, source identity and beneficiary in your builder; show the SDK's readiness instead of deriving your own. ## useSpiceLock An input-inclusive adapter over `useSpiceSupply`. Provide `enabled`, `selectedAsset`, `lockChainId`, `lockTokenSymbol` and `lockBatches`; name an unconfigured lock token with `destinationTokenAddress`. Decimals and logo are optional. `beneficiary`, `onSubmit`, `onLockSuccess` and `onLockError` support product-specific ownership and application updates. The `lockBatches` callback receives `amountWei` in **destination lock-token units**, along with `beneficiary` and `executorAddress`. Return destination-chain protocol calls. The SDK owns conversion and delivery. Do not execute transactions in `onLockExecute`; that override blocks execution. The hook returns the Supply controller plus `destination` and `metadataError`. Duration and protocol lock parameters remain host-owned. `max()` and the shared status/fee fields are available to custom UIs. ## useSpiceAccount ```tsx theme={null} import { useSpiceAccount } from "@spicenet-io/spiceflow-ui"; export function AccountMode() { const { mode, setMode, ownerAddress } = useSpiceAccount(); return ; } ``` Returns `mode` (`embedded` or `external`), `setMode`, `ownerAddress`, `embeddedAddress`, `externalAddress` and whitelist state/actions. A provider with a fixed `mode` prop ignores mode changes. Account ownership does not restrict the controller's funding sources to that wallet. ## useSpiceInventory ```tsx theme={null} import { assetPositionId, useSpiceInventory } from "@spicenet-io/spiceflow-ui"; export function Holdings() { const { assets, loading, refreshing } = useSpiceInventory({ role: "holding" }); if (loading) return

Loading…

; return
    {assets.map(asset =>
  • {asset.symbol}: {asset.balanceFormatted}
  • )}
; } ``` Returns `positions` (assets plus eligibility/reasons), eligible `assets`, `walletAssets`, `rollupAssets`, `loading`, `refreshing`, `error` and `refresh`. Optional owner, chain, token and role filters select the view; roles are `holding`, `deposit-source`, `spend-source`, and `withdraw-destination`. Supply/Lock already read inventory, so do not add a second inventory pipeline around them. ## useSpiceConditionalIntents Prepares fixed conditional actions or supported swaps, signs the reviewed plan, and registers it with a configured service. Returns `prepare`, `prepareSwap`, `sign`, `register`, `authorizeRead`, `get`, `list` and `cancel`. Use the exact prepared/signed object from the same mounted hook. See [Conditional intents](/guides/conditional-intents#conditional-intents) for draft fields, signature requirements, service trust and a typed review example. ## useSpiceLimitOrders Prepares and manages account-funded limit swaps. Accepts optional `{ combineSourceBalances: true }` to fund an eligible account Cash shortfall from wallet balance. Returns `canPrepare`, `canPlace`, `prepareOrder`, `signOrder`, `placeOrder`, `getOrder`, `listOrders`, `cancelOrder`, `status` and `refreshStatus`. See [Limit orders](/guides/conditional-intents#limit-orders) for amounts, placement side effects, expiry and pending-operation handling. ## useSpiceActionActivity Observes backend actions for a wallet. Accepts `(wallet?, enabled?, { pendingOnly? }?)` and returns `actions`, `loading`, `error`, `updatedAt`, `revision` and `refresh`. Supply already exposes its observer as `status.activity`; use this hook for a separate activity view, not a second execution or recovery loop. Availability depends on the configured backend. ## Compatibility Hooks `useSpiceAction` remains a compatibility adapter over Supply. Existing `useSupplyAssets`, `useSupplyQuote` and execution helpers remain exported; new custom actions should start with `useSpiceSupply`. Accepted-action recovery uses backend status, not a browser action journal. See [Migration to 4.19.0](/sdk/migration) before carrying older recovery or execution overrides forward. ## useSpiceExecution The core execution hook. Handles EIP 7702 delegation signing, intent submission, and solver execution polling. It is a lower-level primitive; use `useSpiceSupply` for a protocol action rather than composing a new pipeline. ### Usage ```tsx theme={null} import { useSpiceExecution } from "@spicenet-io/spiceflow-ui"; function CustomDeposit() { const { execute, estimate, clearStatus } = useSpiceExecution(); const handleExecute = async () => { const result = await execute( chainBatches, // destination chain calls tokenAddress, // token being deposited tokenTransferAmount, // amount in wei (progress) => { // progress callback console.log(progress.step, progress.message); }, ); console.log("Execution result:", result); }; return ; } ``` ### Return Value The hook returns `execute` (not `executeGasless`), plus fee estimation, status, and a status reset: ```typescript theme={null} { execute: ( chainBatches: ChainBatch[], tokenAddress: string, tokenTransferAmount: bigint, onProgress?: (progress: ExecutionProgress) => void, signal?: AbortSignal, options?: ExecutionOptions, ) => Promise; // transaction hash when available, otherwise actionId estimate: (chainBatches: ChainBatch[], tokenAddress: string, amount: bigint) => Promise; lastFeeEstimate: FeeEstimateSummary | null; hasEmbeddedWallet: boolean; intentStatus: unknown; clearStatus: () => void; // call between sequential executions (there is no reset()) } ``` > [!NOTE] > > The SDK holds its own concurrency lock and throws on an overlapping `execute` call, so you do not need > a module-level mutex. For multi-step flows, call `clearStatus()` between executions. ### Progress Steps The `onProgress` callback receives updates as execution proceeds: | Step | Description | | - | - | | `idle` | Not started | | `building` | Building the intent | | `signing-delegation` | User signing EIP 7702 delegation | | `signing-intent` | User signing the intent hash | | `submitting` | Submitting to the solver | | `executing` | Solver executing on chain | | `confirming-rollup` | Confirming settlement on the Spicenet rollup | | `success` | Execution complete | | `error` | Something went wrong | *** ## useSpiceAssets Fetch the user's Spice balance from the relayer API. This returns the off chain balance (what the user has deposited via escrow), not on chain wallet balances. ### Usage ```tsx theme={null} import { useSpiceAssets } from "@spicenet-io/spiceflow-ui"; function BalanceDisplay() { const { assets, loading, hasBalance, refetch, getAssetsByChain } = useSpiceAssets({ address: "0x...", supportedChains: [8453, 4114], refetchInterval: 30000, }); if (loading) return

Loading...

; if (!hasBalance) return

No deposits yet

; return (
    {assets.map(asset => (
  • {asset.symbol}: {asset.balanceFormatted} on chain {asset.chainId}
  • ))}
); } ``` ### Config | Param | Type | Default | Description | | - | - | - | - | | `address` | `string` | | Wallet address to query | | `supportedChains` | `number[]` | | Filter to specific chains | | `enabled` | `boolean` | `true` | Enable or disable fetching | | `refetchInterval` | `number` | | Auto refetch interval in ms | ### Return Value | Field | Type | Description | | - | - | - | | `assets` | `Asset[]` | Array of deposited assets with balances | | `loading` | `boolean` | True during initial load | | `error` | `string \| null` | Error message if fetch failed | | `hasBalance` | `boolean` | Whether user has any balance | | `refetch` | `() => Promise` | Manually refetch balances | | `getAssetsByChain` | `(chainId) => Asset[]` | Filter assets by chain | *** ## useSupplyAssets Lists every asset the user can fund a given destination token with — wallet balances and Spice balances, across all supported chains. This is the source-selection half of a custom supply UI (see the [Custom UI Integration](/guides/custom-ui) guide for the full flow). ### Usage ```tsx theme={null} import { useSupplyAssets } from "@spicenet-io/spiceflow-ui"; const { displayAssets, embeddedWalletAddress, externalAddress, isNon7702 } = useSupplyAssets({ address, // user wallet address destinationToken: { chainId: 8453, address: USDC, symbol: "USDC", decimals: 6 }, sourceChains: [1, 8453, 42161], // optional: restrict source chains supportedSourceAssets: ["usdc"], // optional: symbols, addresses or "native" enabled: true, }); ``` ### Return Value | Field | Type | Description | | - | - | - | | `displayAssets` | `(Asset & { _source: "wallet" \| "spice" })[]` | Fundable assets across chains, tagged by source | | `filteredSpiceAssets` | `Asset[]` | Spice balance assets only | | `walletAssets` | `Asset[]` | Wallet balance assets only | | `embeddedWalletAddress` | `` `0x${string}` \| undefined `` | The user's embedded wallet | | `externalAddress` | `` `0x${string}` \| undefined `` | The user's external wallet | | `isNon7702` | `boolean` | True when running without an embedded 7702 wallet | | `refreshSpiceAssets` | `() => Promise` | Refetch Spice balances | | `refreshWalletAssets` | `() => void` | Refetch wallet balances | *** ## useSupplyQuote Resolves routing for a selected source asset: whether it funds the destination token directly or needs a swap, and the quote for it. It remains available for lower-level integrations. New custom UIs should use `useSpiceSupply` to keep quote, fee and execution state together. ### Usage ```tsx theme={null} import { useSupplyQuote } from "@spicenet-io/spiceflow-ui"; const quote = useSupplyQuote({ direction: "input", // amount is what the user pays ("output": what the market receives) selectedAsset, // { asset, amount } or null destinationToken, chainId: destinationToken.chainId, recipient: embeddedWalletAddress, paymentAmount: amount, }); ``` ### Return Value | Field | Type | Description | | - | - | - | | `isQuoting` | `boolean` | Quote in flight | | `quoteError` | `string \| null` | Quote failure | | `estimatedOutput` | `string \| null` | Destination amount for `direction: "input"` | | `estimatedInput` | `string \| null` | Source amount for `direction: "output"` | | `needsSwap` | `boolean` | Source asset must be swapped into the destination token | | `isDirect` | `boolean` | Direct funding, no swap | | `resolvedSwap` | `ResolvedSwap \| null` | The resolved routing swap | | `exactOutputSwap` | `ResolvedExactOutputSwap \| null` | Exact-output variant when quoting by output | | `quoteAmountOut` | `bigint \| null` | Raw quoted output amount | | `resolvedTokenIn` | `Address \| null` | The destination-chain token the funding arrives in | | `selectedIsEquivalent` | `boolean` | Source is the same asset as the destination token on another chain | *** ## useWallet Access wallet connection state and signing actions for the configured provider (Privy). ### Usage ```tsx theme={null} import { useWallet } from "@spicenet-io/spiceflow-ui"; function WalletInfo() { const { address, isConnected, isAuthenticated, provider, actions } = useWallet(); if (!isConnected) return

Not connected

; return (

Connected via {provider}: {address}

); } ``` ### Return Value | Field | Type | Description | | - | - | - | | `isReady` | `boolean` | Provider is initialized | | `isAuthenticated` | `boolean` | User has authenticated | | `isConnected` | `boolean` | Wallet is connected | | `address` | `Address \| undefined` | Connected wallet address | | `provider` | `"privy" \| null` | Active provider | | `actions.signMessage` | `(message) => Promise<{signature}>` | Sign a message | | `actions.signAuthorization` | `(params) => Promise` | Sign EIP 7702 authorization | *** ## Getting the embedded wallet address There is no standalone `useEmbeddedWalletAddress` hook. In 7702 mode the embedded wallet is the on-chain identity; read its address from `useWallet()` or from the provider context: ```tsx theme={null} import { useContext } from "react"; import { useWallet, SpiceFlowProviderContext } from "@spicenet-io/spiceflow-ui"; function EmbeddedWallet() { const ctx = useContext(SpiceFlowProviderContext); const embeddedAddress = ctx?.embeddedWalletAddress; if (!embeddedAddress) return

No embedded wallet

; return

Embedded: {embeddedAddress}

; } ``` *** ## useAssetInput Manage asset selection and amount input state. Used to control the asset selector in deposit and lock flows from your own UI. ### Usage ```tsx theme={null} import { useAssetInput } from "@spicenet-io/spiceflow-ui"; function CustomInput() { const { selectedAsset, setSelectedAsset, assetAmount, setAssetAmount } = useAssetInput(); return ( setAssetAmount(e.target.value)} placeholder="Amount" /> ); } ``` ### Return Value | Field | Type | Description | | - | - | - | | `selectedAsset` | `SelectedAsset \| null` | Currently selected asset | | `setSelectedAsset` | `(asset) => void` | Set the selected asset | | `assetAmount` | `string` | Current amount string | | `setAssetAmount` | `(amount) => void` | Set the amount | *** ## useLockDuration Manage lock duration selection state for `SpiceLock`. ### Usage ```tsx theme={null} import { useLockDuration } from "@spicenet-io/spiceflow-ui"; const durationOptions = [ { label: "1 Month", months: 1 }, { label: "6 Months", months: 6 }, { label: "12 Months", months: 12 }, ]; function DurationPicker() { const { selectedDurationIdx, setSelectedDurationIdx, selectedDuration } = useLockDuration(durationOptions); return (
{durationOptions.map((opt, i) => ( ))}
); } ``` *** ## useSpicePendingDeposits Detect funds stranded in the user's embedded wallet (deposits that were sent but not yet credited to Spice balance). Used by SpiceDeposit internally for recovery flows. ### Usage ```tsx theme={null} import { useSpicePendingDeposits } from "@spicenet-io/spiceflow-ui"; function PendingCheck() { const { pendingDeposits } = useSpicePendingDeposits({ chainIds: [8453, 4114], }); if (pendingDeposits.length > 0) { return

You have {pendingDeposits.length} pending deposit(s) to recover.

; } return null; } ``` *** ## useSpiceBrand Resolve the current theme from the provider's `theme` prop and any component-level `styles`/`dark` overrides. Only used when building custom components that need to match the SDK's look. ### Usage ```tsx theme={null} import { useSpiceBrand } from "@spicenet-io/spiceflow-ui"; function CustomCard({ styles, dark }) { const { primaryColor, dk, palette } = useSpiceBrand(styles, dark); return (
Themed card
); } ``` ### Return Value | Field | Type | Description | | - | - | - | | `dark` | `boolean` | Whether dark mode is active | | `primaryColor` | `string` | Brand accent color | | `theme` | `Theme` | Full theme object | | `dk` | `ResolvedDarkPalette` | Dark palette values | | `palette` | `ResolvedDarkPalette` | Mode resolved palette (light or dark) | *** ## Next Steps > **[Components](/sdk/components)** > > Use prebuilt components for common flows > **[Styling](/sdk/styling)** > > Customize appearance with the brand system > **[API Reference](/api-reference)** > > Explore the REST API for advanced use cases > **[Configuration](/sdk/configuration)** > > Configure chains and providers # Installation & Setup Source: https://spiceflow-docs.spicenet.io/sdk/installation Get started with the Spice Flow SDK in your React application This reference targets stable `@spicenet-io/spiceflow-ui@4.19.0`. See the [migration guide](/sdk/migration) when upgrading an existing integration. ## Installation Install the stable version described by this reference: ```bash theme={null} bun add @spicenet-io/spiceflow-ui@4.19.0 ``` The UI package depends on core 0.8.2. If your application imports core directly, declare that same version explicitly to keep helpers and types aligned. ## Peer Dependencies The SDK requires the following peer dependencies. Install based on your wallet provider: ### Required Dependencies These are always required: ```bash theme={null} bun add react@18 react-dom@18 viem wagmi @tanstack/react-query ``` | Package | Version | Description | | - | - | - | | `react` | `^18.0.0` | React library | | `react-dom` | `^18.0.0` | React DOM | | `viem` | `>=2.21.0` | Ethereum interface library | | `wagmi` | `>=2.12.0` | React hooks for Ethereum | | `@tanstack/react-query` | `>=5.0.0` | Data fetching library | > [!NOTE] > > Keep the app on React 18 and resolve peers within the declared ranges. Do not bypass peer conflicts > as a default setup step. Reuse an existing compatible wallet/provider stack. ### Wallet Provider The SDK uses Privy for wallet connections: ```bash theme={null} bun add "@privy-io/react-auth@>=2.24.0 <4.0.0" ``` | Package | Version | Provider | | - | - | - | | `@privy-io/react-auth` | `>=2.24.0 <4.0.0` | Privy | *** ## Complete Installation ```bash theme={null} bun add @spicenet-io/spiceflow-ui@4.19.0 @spicenet-io/spiceflow-core@0.8.2 "@privy-io/react-auth@>=2.24.0 <4.0.0" react@18 react-dom@18 viem wagmi @tanstack/react-query ``` *** ## Basic Setup ### 1. Mount the Provider Stack `SpiceFlowProvider` reads wallet state from Privy, wagmi, and react-query — it does not mount them. Your app provides all four, in this order. The SDK injects its stylesheet automatically in the browser; an existing explicit `styles.css` import remains supported: ```tsx theme={null} {/* Your app components */} ``` See [Quick Start Step 2](/quickstart#step-2-setup-providers) for the full wagmi and Privy configs. Already running Privy? Reuse your existing `PrivyProvider` — add the chains you enable to its `supportedChains` and your wagmi config. ### 2. Add Wallet Connection Use the `ProviderLogin` component to let users connect their wallet: ```tsx theme={null} import { ProviderLogin } from "@spicenet-io/spiceflow-ui"; function Header() { return (
); } ``` ### 3. Add an Action and Account Menu Use `SpiceSupply` for protocol calls, or `useSpiceSupply` for your own UI. See the complete [Quick Start example](/quickstart#step-3-add-a-protocol-action). For account funding and Spice-balance withdrawals, the combined component needs no modal wiring: ```tsx theme={null} import { SpiceAccount } from "@spicenet-io/spiceflow-ui"; export function AccountMenu() { return ; } ``` The SDK supplies default fee/funding batches, token metadata, venues and styles. Protocol addresses, ABIs, recipient policy and any required application updates remain yours to define. *** ## Next.js Considerations If you're using Next.js with Server-Side Rendering (SSR), wrap components that use Spice Flow in a client component: ```tsx theme={null} "use client"; // Add this directive at the top import { SpiceFlowProvider } from "@spicenet-io/spiceflow-ui"; ``` Or check for the window object: ```tsx theme={null} if (typeof window === 'undefined') { return
Loading...
; } ``` *** ## Environment Variables Store your provider credentials in environment variables: ```env theme={null} NEXT_PUBLIC_PRIVY_APP_ID=your_privy_app_id ``` Then use them in your code: ```tsx theme={null} ``` The app ID belongs on your own `PrivyProvider`. `SpiceFlowProvider` has a `privyAppId` prop, but it has no effect. *** ## Next Steps > **[Components](/sdk/components)** > > Learn about all available components and their props > **[Hooks](/sdk/hooks)** > > Use React hooks to build custom UIs > **[Configuration](/sdk/configuration)** > > Configure wallet providers, chains, and more > **[Quick Start Guide](/quickstart)** > > Build your first Spice Flow app step-by-step # Migration to 4.19.0 Source: https://spiceflow-docs.spicenet.io/sdk/migration Update action controllers, Cash funding, conditional orders and recovery This guide targets stable `@spicenet-io/spiceflow-ui@4.19.0`. Keep your app's layout and wallet-provider stack; update its SDK contracts and status handling. The package depends on `spiceflow-core@0.8.2`. If your app imports core directly, align that dependency rather than keeping a second, older core API. ## Migration Map | Existing integration | 4.19.0 action | | - | - | | Manual quote, fee, batch and executor state | Use `useSpiceSupply`; render its returned state | | `enabled: true` on Supply | Optional now; defaults to true. Keep `enabled: isOpen` for mounted hidden forms | | Custom Lock executor | Use `useSpiceLock` with destination-only `lockBatches`; this hook still requires `enabled` | | `onPayExecute` / `onLockExecute` | Move contract calls to `buildActionCalls` / `lockBatches`, and app updates to awaited success callbacks. These execution overrides block submission | | Async validation in `onSubmit` | Move it to awaited `beforeSubmit(review)`; `onSubmit` is a notification | | Host purchase funded from several balances | Use `fundingSources` with an input-inclusive budget; see [Cash funding](/guides/cash-funding) | | Matching wallet and escrow balances shown separately | Opt into `combineSourceBalances`; use the controller's available balance and Max | | Retrying after a timeout or closed modal | Check `status.pending`, `status.recoveryError` and `refreshStatus()` before a new submission | | `actionExecution.wallet: "external"` on the Supply hook | Supported for eligible routed external callers, with restrictions below | | `sourceFundingRecoveryKey`, `sourceFundingRecovery`, Lock `recoveryKey` | Still compatibility no-ops; backend recovery does not reactivate the old browser journal | | `buildSpicenetBatch`, `feeExecutionMode` | Compatibility options; omit for ordinary new integrations and let the SDK build funding and fee calls | | Three account/deposit/withdraw components plus modal state | Optional `` composes all three | ## Basic Action Setup A selected-asset Supply controller needs `destinationToken`, `selectedAsset`, and `buildActionCalls`. The destination needs `chainId` and `address`; metadata resolves automatically. The app owns protocol addresses, ABIs, beneficiary selection and application updates. The SDK owns inventory, conversion, funding, fees, Max and execution. See the [complete example](/guides/custom-ui). Use `"input"` for principal plus fees, `"input-inclusive"` for a total source budget, and `"output"` for a fixed destination payment. Lock uses input-inclusive. Builder amounts are destination-token bigints, not source-token units. Show fees using the SDK's returned currency and decimals. Keep `network="mainnet"` explicit for production; the default is testnet. `nativeChainId` remains the app-chain setting for account deposits and withdrawals. A client `rpcOverrides` entry wins; otherwise SDK reads use the built-in endpoint. Wagmi transports alone do not configure SDK reads. ## Cash and Scheduled Actions * `combineSourceBalances` is opt-in and combines eligible matching wallet and escrow positions. * `fundingSources` supports a native account purchase from nominated positions, with conversions and fees inside a budget denominated in the destination chain's supported USDC. It requires 7702 mode, `selectedAsset: null`, and input-inclusive semantics. See [Cash funding](/guides/cash-funding). * `useSpiceLimitOrders` prepares, signs, places and manages supported limit orders. Its optional combined-balance mode can fund an escrow shortfall from the account wallet. * `useSpiceConditionalIntents` prepares fixed actions or supported swaps, collects signatures, and registers them with a configured conditional-intent service and evaluator. These are separate interfaces in this release. See [Conditional intents and limit orders](/guides/conditional-intents). SDK exports alone do not establish that a custom backend supports these services. Use the configured API's supported capabilities; do not replace an unavailable service with a host execution loop. ## Wallets and Callers Matching-token external actions on the destination chain execute directly through the SDK, with native gas paid by that wallet. Calls are sequential, not atomic. This path excludes fee-only, proceeds-funded and zero-amount payments. By default, routed sources use the embedded execution wallet. A protocol that credits only `msg.sender` must execute from the intended owner's wallet; setting `recipient` does not change that contract behavior. For a routed action that must execute from the connected external wallet, `useSpiceSupply` accepts `actionExecution: { wallet: "external", recoveryKey: "your-action-key" }`. The SDK owns funding and then sends the destination calls from that wallet. This requires a positive ERC-20 Supply/Pay action, a ready external signer, and explicit `rpcOverrides` for the destination and any wallet-source chain. It does not support fee-only actions, proceeds funding, native destination tokens or `prependSourceEscrow`. This hook option is separate from the deprecated `onPayExecute` override. Do not implement a second host transaction sender around it. ## Recovery and Completion The configured backend can automatically continue accepted actions when it advertises that capability. The SDK observes existing action/step status and exposes `status.actionId`, `status.intentId`, `status.autoContinue`, `status.pending`, `status.recoveryChecking` and `status.recoveryError`. An unresolved submission can expose `status.submissionStatus === "unknown"` and its submission identity. Use `refreshStatus()` to reconcile it, and use `canExecute` rather than bypassing the controller's readiness. Closing a modal, hiding a tab, or losing an HTTP response does not cancel an accepted action. This is backend recovery, not a persisted browser action journal. A reload does not guarantee replay of an application's previous success callback. Reload application state from authoritative records. Direct sequential external-wallet calls also do not gain durable browser progress; inspect receipts before retrying a partially completed sequence. Supply/Pay verifies the final destination receipt and reports that transaction hash. It can finish the user-facing destination action while later backend settlement continues. A source funding hash is not the destination result. Account deposits still require their relevant Spice-credit checks. The controller awaits `onSuccess` / `onLockSuccess`. If only the application update fails, `retryFinalization()` retries that update without resending funds. Confirmed results remain associated with the submitted action while inputs change or a mounted form is hidden. Keep callback handling idempotent and separate application errors from transaction failures. Accepted immediate-action plans have no arbitrary UI timer expiry; changed inputs or invalid funding still require a valid plan. Scheduled orders and conditional authorizations have their own explicit expiry and trigger-freshness contracts. ## Upgrade Checks 1. Build against the published package and its peer dependency ranges. 2. Verify source identity with `assetPositionId`; retain owner, location, chain and raw token units. 3. Check amount modes, combined balances if enabled, Max, fee currency and insufficient funds. 4. Verify direct and routed execution for your contract's caller and beneficiary semantics. 5. Check pending/unknown submissions, status-refresh failure and application-finalization retry. 6. For scheduled actions, review the exact fixed calls, budget, minimum output, expiry and signatures before registration. Verify service support and cancellation behavior on your configured backend. Deposit and Withdraw retain their distinct callback contracts. Do not apply the Pay/Lock override migration to account deposit/withdraw callbacks. # Styling and Theming Source: https://spiceflow-docs.spicenet.io/sdk/styling Customize the appearance of Spice Flow components to match your brand ## Stylesheet SDK 4.19.0 injects its stylesheet once when loaded in a browser. The explicit `@spicenet-io/spiceflow-ui/styles.css` import is optional and remains supported. Server-side imports do not access `document`. Keep any app-specific bundler or wallet-dependency polyfills. ## Theme System Set your brand once on `SpiceFlowProvider`. Every SDK component inherits it automatically. ```tsx theme={null} {children} ``` ### SpiceTheme Options | Option | Type | Default | Description | | - | - | - | - | | `primaryColor` | `string` | `"#EA4B4B"` | Accent color — buttons, highlights, active states | | `dark` | `boolean` | `false` | Dark or light mode | | `shell` | `string` | Mode default | Modal outer background | | `card` | `string` | Mode default | Card and input area background | | `text` | `string` | Mode default | Primary text color | | `textMuted` | `string` | Mode default | Secondary/label text color | | `border` | `string` | Mode default | Border color | | `borderRadius` | `string` | `"8px"` | Border radius applied across all components | | `fontFamily` | `string` | `"Helvetica Neue"` | Primary font family | | `appName` | `string` | `"Spicenet"` | App name shown in modal headers | | `logo` | `ReactNode` | | Custom logo for modal headers | Surface tokens (`shell`, `card`, `text`, `textMuted`, `border`) have sensible defaults per mode — only set what you want to override. *** ## Dark Mode ```tsx theme={null} // Dark // Light ``` ### Default Surface Colors | Token | Dark | Light | | - | - | - | | `shell` | `#141414` | `#ffffff` | | `card` | `#1e1e1e` | `#f9fafb` | | `text` | `#ffffff` | `#111827` | | `textMuted` | `#888888` | `#6b7280` | | `border` | `{primaryColor}33` | `{primaryColor}22` | *** ## Custom Surfaces Override any surface token alongside the mode: ```tsx theme={null} ``` *** ## Per-Component Override Pass `styles` to any component to override the provider theme for that instance only: ```tsx theme={null} // Different accent on a single modal // Force dark on one component even if provider is light // Full button + input customisation ``` ### Available `styles` keys per component | Component | Keys | | - | - | | `SpiceDeposit` | `primaryColor`, `button.backgroundColor`, `button.color`, `button.borderRadius`, `inputVariant`, `fontFamily` | | `SpiceWithdraw` | `primaryColor`, `button.backgroundColor`, `button.color`, `fontFamily` | | `SpiceSupply` | `primaryColor`, `button.backgroundColor`, `button.color`, `inputVariant` | | `SpiceLock` | `primaryColor`, `button.backgroundColor`, `button.color`, `fontFamily` | | `AccountDisplay` / `SpiceAccount` | `primaryColor`, `fontFamily`, `button` for account actions | `inputVariant` accepts `"light"` or `"dark"` to override the input field style independently of the global mode. *** ## Fonts The SDK uses two fonts: * **Helvetica Neue** — all body text, labels, headings, descriptions * **IBM Plex Mono** — amounts, addresses, hashes, button labels Override the body font via `theme.fontFamily`. The monospace font is always used for technical displays. *** ## Examples ### Orange Dark ```tsx theme={null} ``` ### Blue Light ```tsx theme={null} ``` ### Green Dark with Custom Surfaces ```tsx theme={null} ``` ### Sharp Trading UI ```tsx theme={null} ``` *** ## Building Custom Components Use `useSpiceBrand` to match the SDK theme inside your own components: ```tsx theme={null} import { useSpiceBrand } from "@spicenet-io/spiceflow-ui"; function CustomCard() { const { primaryColor, dark, palette } = useSpiceBrand(); return (

Custom card

Matches SDK palette

); } ``` `palette` is the fully-resolved palette for the current mode — dark or light values depending on `dark`. It exposes: `shell`, `cardBg`, `inputBg`, `hoverBg`, `textPrimary`, `textSecondary`, `inputText`, `inputPlaceholder`, `cardBorder`, `inputBorder`, `buttonBorder`, and semantic tokens (`successBg`, `errorBg`, `warningBg`, `infoBg`, and their border/text variants). `dk` is the raw dark palette, useful when you need dark-specific values regardless of mode (e.g. for overlay rendering). *** ## Next Steps > **[Components](/sdk/components)** > > See all available components > **[Configuration](/sdk/configuration)** > > Configure providers and chains > **[Examples](/guides/native-swap)** > > See styled examples in action > **[Best Practices](/guides/best-practices)** > > Follow integration best practices