> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kuru.io/llms.txt
> Use this file to discover all available pages before exploring further.

# KuruTradingWallet

> Version-2 execution rules, triggers, TWAP, and signed data.

`KuruTradingWallet` is immutable EIP-7702 delegation logic for disposable one-click Spot trading EOAs such as Mera wallets. Each EOA owns its nonce, trigger, and TWAP state. There is no shared executor owner, submitter allowlist, or Mera-specific authorization rule.

## Authorization and Relay Model

The user grants the disposable EOA a caller-selected permission mask in `AccountCore`, either directly or through `authorizeAccountSignerBySig`. Trading requires live `TRADE` permission; the wallet contract does not force that permission during enrollment and does not grant itself `ADMIN`.

Every user-authorized trading intent is EIP-712 signed by the delegated EOA. The domain is:

```text theme={null}
name: KuruTradingWallet
version: 2
chainId: current chain
verifyingContract: delegated EOA
```

Recovering the signature to `address(this)` both authenticates the Mera key and isolates signatures between delegated EOAs. Calls to the implementation address cannot reuse an EOA-domain signature. The contract also implements ERC-1271 for protocols that need to validate a wallet signature.

Any address may relay immediate orders and trigger creation/cancellation. Relayers pay gas or route through a sponsor; relayer identity is deliberately absent from the signed permission model.

## Wallet-signed data

`emitSignedData(bytes data, uint256 nonce, uint64 deadline, bytes signature)` lets any relayer
publish opaque bytes authenticated by the delegated wallet's EOA key. It emits
`SignedDataEmitted(address indexed wallet, uint256 indexed nonce, bytes data)` from the delegated
EOA and returns the signed digest. This can publish a signer name during onboarding, even before
AccountCore enrollment; it does not create a signer or prove current AccountCore authorization.

Use the domain above (version `2`) and the EIP-712 primary type `EmitSignedData`:

```text theme={null}
EmitSignedData(bytes data,uint256 nonce,uint64 deadline)
```

Supply raw bytes as `data` to the typed-data signing library; Solidity includes `keccak256(data)`
in the struct hash. For a simple name, use UTF-8 bytes, e.g. `Mera bot` is `0x4d65726120626f74`.
Applications needing richer metadata should agree on a versioned encoding. The contract does not
interpret, restrict, or store the bytes; empty bytes are valid. All emitted data is public.

Read `signedDataNonce()` before signing. It starts at zero and must match exactly, then increments
on success. Nonces are wallet- and chain-local and independent of order/trigger/TWAP nonces.
`deadline` is an inclusive Unix timestamp in seconds. The signature binds every field, chain, and
wallet; failed submissions consume no nonce. Any relayer can submit the identical signed message
first, but cannot substitute its contents. Sequential nonces prevent an older publication from
being replayed after a newer one; two messages signed with the same nonce race and only one succeeds.

Indexers should identify the wallet by the emitting address, recognize its trusted delegation, and
correlate it with AccountCore signer events for permissions. A name is a wallet-authored label, not
proof of identity, uniqueness, or approval by an account owner. Publishing and AccountCore signer
enrollment are separate operations; clients must arrange batching if they need atomic onboarding.
Existing delegated wallets need to delegate to the newly deployed implementation to use this method.

## Immediate Orders

`executeReplaceBySlotPacked` and `executeBatch` bind the account id, verified market, target account's root authorization epoch, millisecond nonce, deadline, client order id, optional builder configuration, payload hashes, and expected order ids.

Immediate nonces are monotonic per delegated EOA and chain. A nonce must be greater than `lastSeenOrderNonce` and no further in the future than `block.timestamp * 1000 + maxFutureNonceSkewMillis`. This preserves stale-quote dropping without central executor state.

## Triggers

Trigger creation uses unordered one-shot nonces, allowing independent standing orders to execute in any order. Creation authenticates the signature once and stores only execution state: account, market, epoch, expiry, client ID, builder, action/status, and payload. The creation nonce, deadline, and condition commitment remain available from transaction calldata and `TriggerCreated`; they are not retained in `getTrigger`. No signature is stored or recovered again at firing.

`conditionHash` remains opaque. An authorized keeper evaluates the condition off-chain and calls `executeTrigger`. Among trigger APIs, only firing consults the immutable `KeeperAuthority`; creation and cancellation remain permissionless relays.

Before firing, the wallet verifies:

1. the trigger is active and unexpired;
2. the caller is a current keeper;
3. the market remains a verified Spot orderbook;
4. the target account's root authorization epoch is unchanged and `TRADE` remains live; and
5. every signed slot order-id binding still matches live orderbook state.

The wallet marks the trigger fired before the external orderbook call. Transaction atomicity restores the active state if validation or forwarding reverts. Expired triggers are marked `EXPIRED` when a keeper attempts to fire them.

## Keeper Authority

`KeeperAuthority` is a non-proxy, independently owned periphery registry. Its owner can grant or revoke keepers and nominate a new administrator, but ownership changes only after the nominee accepts. Contract main `0e7eb76` additionally disables ownership renunciation; this later source change is not evidence that the KeeperAuthority address in the September 29 deployment was replaced. The wallet stores an immutable registry address, so changing registries requires deploying and delegating new wallet logic.

Keeper trust is narrow but real: a malicious authorized keeper can falsely claim an opaque condition was met and fire any active trigger. It cannot change the signed action, market, account, builder, expiry, or order-id bindings, and it has no special authority over immediate orders.

## Development deployment and state trust

Deploy `KuruTradingWallet` with the intended AccountCore, existing keeper registry, and future-nonce skew. This development version does not preserve earlier testnet wallet storage or signatures. It uses domain version **2** and fresh ERC-7201 root `kuru.storage.KuruTradingWallet.v2`, with nonce/trigger/TWAP state keyed by chain ID. The domain change prevents version-1 intents replaying against reset nonce state; chain-keyed storage prevents standing orders executing on a different chain after creation.

Stored actions are authenticated state, not proofs reverified every time they are read. Only authenticated creation can initialize them; payload/configuration are immutable thereafter. This assumes the EOA has not delegated to incompatible/untrusted code that can alter its storage. Changing delegation is not cancellation or state repair. No migration adapter or old-bytecode compatibility gate is provided. Retain independent authorization epochs and explicit cancellation for revocation. The wallet's ERC-1271 endpoint remains available to external callers, but its own intents use EOA ECDSA recovery, not a mutable external ERC-1271 validator.

All mutating entrypoints share one transient reentrancy lock, cleared on return and rolled back on revert. The intended runtime must support EIP-1153, as the repository's OrderBook already requires.

## Typed cancellation and FOK orders

The following methods use the existing `IntentHeader` and monotonic immediate-order nonce. Each has a
distinct flat EIP-712 type; the exact schemas and executable calldata examples are in
[`examples/trading-wallet.mjs`](https://github.com/Kuru-Labs/kuru-contracts-perps/blob/42ef525/docs/examples/trading-wallet.mjs). All amounts are integer market units:
base quantity uses `sizePrecision`, limit price uses `pricePrecision`, and fees use the existing PPS scale.
On the size-precision branch, raw base-token units equal normalized size times `baseSizeMultiplier`;
do not submit token-wei quantities directly when that multiplier is not one.

| Method | Signed payload and execution |
| - | - |
| `executeCancelOrdersById(header, orderIds, signature)` | Hashes the array as `keccak256(abi.encode(orderIds))`, resolves IDs in the market's 62 slots, and calls one cancellation-only `batch`. |
| `executeCancelAll(header, signature)` | Cancels all resting orders live at execution in this account/market, including those placed after signing. |
| `executeFokSwap(header, order, signature)` | `FokOrder(side, quantity, limitPrice)` becomes one native `FOK` order, with `NONE` execution instruction, zero `minSizeAfterBlock`, and no cancellation slots. |

Cancellation requires a zero builder and zero fee. ID lists must contain 1–62 unique, nonzero, live IDs
belonging to the signed account/market; missing/stale IDs revert the entire operation and nonce update.
Partially filled live IDs cancel their remainder. No slot hints or reverse mapping are needed.
Native cancellation pause rules apply: soft-paused markets allow cancellation; hard-paused markets do not.
Cancel-all does not cancel triggers, TWAPs, passive positions, or orders in another market.

All three methods return their signed digest and emit `IntentExecuted`. The native cancel-all entrypoint
does not take a client ID: its book logs carry no signed ID, while the wallet event retains it. Correlate
these by transaction, account, market, and call order. ID cancellation forwards the client ID to `batch`.

Dedicated FOK methods are base-sized marketable-limit orders, not exact-input `swap()` calls. They inherit
native dust tolerance: a successful FOK may leave an unfilled sub-executable remainder. They also inherit
native self-order cancellation and fee/builder settlement. They do not return a filled amount. Use the
actual packed trade records for fills, VWAP, and fees; a limit price is not a fee-inclusive spending cap.
Existing generic `executeBatch` still supports GTC, IOC, and FOK unchanged.

`createFokSwapTrigger(header, triggerExpiry, conditionHash, order, signature)` uses an unordered nonce
and stores `TriggerAction.FOK_SWAP = 3`. Fire it through keeper-only
`executeTrigger`, cancel through the existing signed `cancelTrigger`, and retain the existing expiry,
epoch, and live authorization checks. Its signature is checked at creation only. A failed native FOK
rolls back `FIRED` so the trigger remains retryable until expiry. No new condition verification is introduced.
FOK trigger payloads are 17 packed bytes (`uint8 side`, `uint96 quantity`, `uint32 limitPrice`),
stored inline in one word; they are not `abi.encode(FokOrder)` blobs.

## Persistent TWAP

`createTwap(header, config, signature)` signs the header plus:

```solidity theme={null}
struct TwapConfig {
    ISpotOrderBook.NativeSide side;
    uint96 totalQuantity;
    uint32 limitPrice;
    uint64 startTime;
    uint32 intervalSeconds;
    uint32 sliceCount;
}
```

The start must be in the future. Interval/count/quantity must be positive, quantity must be exactly
divisible by the count, and the derived end time must fit `uint64`. Schedule timestamps and deadlines
are seconds; nonce skew remains milliseconds. The header deadline only bounds creation. Tick/notional
validation and current builder approval remain authoritative in the OrderBook at execution; the client
should preflight these constraints before creating a schedule. No funds are reserved on creation.

For slice `i`, the execution bucket is `[startTime + i * intervalSeconds, startTime + (i + 1) * intervalSeconds)`.
Only a current keeper can call `executeTwapSlice(twapId, executionReportHash)`. The wallet checks
the current AccountCore epoch, live `TRADE`, and verified market. Creation already authenticated the
immutable configuration, so execution neither rebuilds the creation digest nor recovers its signature.
It computes the current bucket itself and executes `totalQuantity / sliceCount` at the static signed limit.
Earlier missed buckets are skipped; there is no catch-up, quantity rollover, or resting child order.

One successful native FOK consumes a bucket. Failure reverts all progress (including skipped-range events)
and allows retry within the same bucket. The next successful attempt skips any now-past buckets. Progress
advances before the market call under the shared transient reentrancy guard.

The signed digest is `twapId`. Child client IDs are:

```solidity theme={null}
keccak256(abi.encode("KURU_TWAP_SLICE", twapId, parentClientOrderId, uint32(sliceIndex)))
```

`getTwap` returns `TwapState(accountId, market, authNonce, clientOrderId, builder, builderFeePps,
config, status, nextSliceIndex, successfulSliceCount)`. Creation nonce/deadline are in `TwapCreated`,
not in the getter. Status values
are `NONE=0`, `ACTIVE=1`, `CANCELED=2`, `FINISHED=3`. After schedule end, the getter projects `FINISHED`
and `nextSliceIndex=sliceCount` without writing storage. `finalizeTwap` is permissionless after end and
persists that transition; it reverts for unknown/already-terminal schedules. Executing the last bucket
also persists `FINISHED`.

`cancelTwap(accountId, authNonce, nonce, deadline, twapId, signature)` uses a fresh unordered nonce and
the current AccountCore epoch/authorization. It may cancel a stored plan signed under an earlier epoch.
An already time-expired plan must be finalized instead. Cancellation stops future slices.
AccountCore revocation/epoch changes also prevent execution; changing delegation alone
does not cancel a plan, because the EOA's storage survives.

TWAP execution state is packed into five words, separately from its public input/getter structs.
The stored slice quantity is computed once at creation; `getTwap` reconstructs total quantity from
slice quantity times count. No creation signature, deadline, or nonce is retained per schedule.
Creation/cancellation share the chain-local `usedTriggerNonce` mapping. Slice execution consumes no new
user intent nonce and does not advance `lastSeenOrderNonce`.

## Events and keeper integration

Index by `(chain, wallet address, twapId)`, not by implementation address. `TwapCreated` contains the header
and config; `TwapSlicesSkipped` records half-open `[fromSlice,toSlice)` ranges; `TwapSliceExecuted`
contains the index, requested nominal quantity, derived client ID, keeper, and opaque report hash.
`TwapFinished` contains successful and skipped counts; `TwapCanceled` identifies the account and wallet.
There is deliberately no onchain `filledQuantity` or `remainingQuantity` field.

The service should:

1. Replay canonical creation/cancellation/finish logs and maintain an index of due schedules.
2. Use chain timestamps and live `getTwap` state; simulate immediately before submitting.
3. Maintain a pending transaction per `(chain, wallet, twapId, sliceIndex)`, reconcile receipts/replacements
   before retrying, and stop retries when that bucket expires. Competing keeper submissions are safe.
4. Reconcile reorgs by block hash/log identity, and finalize schedules whose last buckets expired.
5. Decode the specific deployed market's packed logs and correlate by wallet/account/market
   and child client ID. Each packed cycle emits the run appended first, then the other run.
   Filled size comes from `TradesPacked`; a nonzero `OperationOutcome` is only on the match-end
   record or a book sentinel. Show successful/skipped slices separately from actual filled base, VWAP, and fees.
   `IntentExecuted` means the immediate signature was accepted. `TriggerFired` means the trigger is
   terminal and is not retried. Both are emitted when the orderbook call returns, including when that
   order was not installed. Join them to packed logs on transaction, market, `accountId`, and
   `clientOrderId`. `nonce` on `IntentExecuted` is the new `lastSeenOrderNonce`; every immediate nonce
   at or below it is consumed. A resting order still comes only from a live `BookUpdatesPacked` record.

Do not claim nominal successful-slice quantity as the actual fill: dust is explicit native behavior.
For this branch's 64-byte packed trade records, filled size remains normalized; convert to raw base
units using the market's multiplier before reconciling token balances. Use execution prices and the
deployed market's fee/rounding rules for quote value and VWAP, not the schedule's limit price.
Overlapping schedules compete for balances. Dedicated trading subaccounts avoid unintended interaction
with maker orders. One transaction per slice and up to 62 getter calls per ID cancellation are the main
gas costs. Frontend and keeper-service deployment are separate from this contract change.

Use the [SDK signing guide](/sdk/trading-wallet) for integration and [signer lifecycle](/sdk/signer-lifecycle) for nonce allocation.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.