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

# EIP-712 Signing Reference

> Construct low-level EIP-712 typed data when you are not using SDK signing helpers or need to debug permit signatures.

For the complete documentation index, see [llms.txt](https://docs.shodai.network/llms.txt).

This page documents the signed authorization layer used by the onchain implementation.

The EIP-712 payloads connect offchain agreement definitions and user authorization to onchain deployment and input submission. These signatures are what allow the execution engine to verify that a deployment or input was authorized by the correct party.

For the conceptual architecture, see [Architecture](/system-architecture/overview). For the API-assisted deployment and operation flow, see [Workflow](/workflow/deploy-an-agreement).

Use this page only when you need the exact low-level signing inputs for `POST /v0/agreements/deploy-with-permit` or `POST /v0/agreements/{id}/input`.

For normal TypeScript integrations, prefer the [TypeScript client reference](/sdks/typescript-client): `deployAgreementWithPermit(...)` signs and submits deployment permits, and `submitAgreementInputWithPermit(...)` signs and submits input permits. Use this reference when you are constructing typed data directly, verifying SDK helper behavior, or debugging a signing mismatch.

## Deploy signing reference

The deployment permit is signed over derived on-chain parameters, not over the raw deployment request body. If you use `deployAgreementWithPermit(...)`, the SDK performs this derivation and signing for you. For raw API deployment, use the same supported `chainId` in deployment preflight, EIP-712 domain construction, and the deploy-with-permit request body.

### Deploy typed data

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const domain = {
  name: "AgreementFactory",
  version: "1",
  chainId,
  verifyingContract: factoryAddress,
};

const types = {
  PermitAgreementWithActions: [
    { name: "docUri", type: "string" },
    { name: "docHash", type: "bytes32" },
    { name: "initialState", type: "bytes32" },
    { name: "inputDefsHash", type: "bytes32" },
    { name: "transitionsHash", type: "bytes32" },
    { name: "initVarsHash", type: "bytes32" },
    { name: "verifiersHash", type: "bytes32" },
    { name: "actionsHash", type: "bytes32" },
    { name: "nonce", type: "uint256" },
    { name: "deadline", type: "uint256" }
  ]
};

const message = {
  docUri,
  docHash,
  initialState,
  inputDefsHash,
  transitionsHash,
  initVarsHash,
  verifiersHash,
  actionsHash,
  nonce,
  deadline,
};
```

### Deploy permit nonce

Set `message.nonce` to the current value of `AgreementFactory.nonces(signer)` on the target chain before signing. Read it through a live `publicClient`/RPC connected to the same chain as `domain.chainId` and `factoryAddress`; do not hardcode `0`.

A successful deploy permit consumes that factory nonce, so deploy signatures are single-use. If the signer nonce changes for any reason, including a previous successful deploy permit, the signature becomes stale and must be regenerated.

### Exact derivation rules

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
docUri = providedDocUri ?? `ipfs://agreement/${agreement.metadata.id}`
docHash = keccak256(stringToHex(JSON.stringify(agreement)))
initialState = keccak256(stringToHex(agreement.execution.initialize.initialState))
```

The remaining fields are hashes of ABI-encoded contract parameters:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
inputDefsHash = keccak256(abi.encode(inputDefs))
transitionsHash = keccak256(abi.encode(transitions))
initVarsHash = keccak256(abi.encode(initVars))
verifiersHash = keccak256(abi.encode(verifiers))
actionsHash = keccak256(abi.encode(actions))
```

Those arrays are constructed from the authored agreement like this:

| Array         | Source                                                        | Derivation                                                                                                                                                         |
| ------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `inputDefs`   | `agreement.execution.inputs`                                  | Each input ID becomes `keccak256(stringToHex(inputId))`; each field ID becomes `keccak256(stringToHex(fieldName))`; issuer constraints become on-chain conditions. |
| `transitions` | `agreement.execution.transitions`                             | `fromState = keccak256(stringToHex(from))`; `toState = keccak256(stringToHex(to))`; `inputId = keccak256(stringToHex(transition.conditions[0].input))`.            |
| `initVars`    | Variables referenced by `agreement.execution.initialize.data` | Each variable ID becomes `keccak256(stringToHex(variableName))`; each variable value is ABI-encoded according to its field type.                                   |
| `verifiers`   | Verifier registrations supplied for deployment                | Each verifier registration binds a verifier key to the verifier contract address installed during agreement initialization.                                        |
| `actions`     | `agreement.execution.actions`                                 | `fromState` and `inputId` are hashed to bytes32; `target`, `value`, and `data` come from the resolved action call.                                                 |

Critical rule when participant mappings are present:

* Do not sign against raw caller input when participant mappings change any value that is hashed or encoded into the signed message.
* Sign against the effective post-mapping values instead.
* For deploy, the safe source of truth is the normalized `variables` object returned by `POST /v0/agreements/validate` for the exact deployment payload you plan to submit.

For example, if `agreement.execution.initialize.data` references participant-backed variables such as `partyAEthAddress` or `partyBEthAddress`, and those addresses are supplied through `participants`, the final mapped wallet addresses still need to be reflected in the `initVars` used for signing.

Field type encoding for `initVars` follows these rules:

* `string`, `dateTime`, `signature` → `abi.encode(string)`
* `address` → `abi.encode(address)`
* `uint256` → `abi.encode(uint256)`
* `bool` → `abi.encode(bool)`
* `bytes32`, `txHash` → `abi.encode(bytes32)`

### Factory address source

The TypeScript helpers resolve the correct factory for the selected chain through the protocol SDK registry. When constructing typed data manually, use the factory address for the same `chainId` that you pass in the EIP-712 domain.

Current factory addresses from the protocol SDK registry:

* Base Mainnet (`chainId = 8453`): `0x76dAA59C02d902e7063E6328D2E64ACee6CC121e`
* Linea Sepolia (`chainId = 59141`): `0x26Ff3AdEC23fC5778f190371B1CcCadDa74e26c8`
* Linea Mainnet (`chainId = 59144`): `0xB772Ea12546fd7153Bf1F5ED7266B8faB0dAD6C9`
* Base Sepolia (`chainId = 84532`): `0x76dAA59C02d902e7063E6328D2E64ACee6CC121e`
* Ethereum Sepolia (`chainId = 11155111`): `0x76dAA59C02d902e7063E6328D2E64ACee6CC121e`

For factory and implementation contract addresses with verified source links, see [Contracts](/system-architecture/contracts).

### Signing call

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const signatureHex = await walletClient.signTypedData({
  account,
  domain,
  types,
  primaryType: "PermitAgreementWithActions",
  message,
});
```

Then split `signatureHex` into:

* `r = 0x...`
* `s = 0x...`
* `v = 27` or `28`

If the agreement JSON, `initValues`, `docUri`, chain, factory address, signer nonce, or deadline changes, the signature must be regenerated.

The TypeScript client uses a one-hour default permit lifetime through `computeDefaultDeadlineSeconds()`. Low-level signing calls still require you to pass an explicit `deadline`.

## Input signing reference

The input permit is signed over the hashed input ID and an ABI-encoded payload. If you use `submitAgreementInputWithPermit(...)`, the SDK performs this derivation and signing for you. For raw input submission, read the deployed agreement record first and use its `chainId` and contract address in the EIP-712 domain.

### Input typed data

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const domain = {
  name: "AgreementEngine",
  version: "1",
  chainId,
  verifyingContract: agreementAddress,
};

const types = {
  PermitInput: [
    { name: "inputId", type: "bytes32" },
    { name: "payload", type: "bytes" },
    { name: "nonce", type: "uint256" },
    { name: "deadline", type: "uint256" }
  ]
};

const message = {
  inputId,
  payload,
  nonce,
  deadline,
};
```

### Exact derivation rules

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
inputIdBytes32 = keccak256(stringToHex(inputId))
payload = abi.encode(dataFields)
```

Where `dataFields` has this contract shape:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
type DataField = {
  id: bytes32,
  fType: uint8,
  data: bytes,
}
```

Build it from the authored input schema like this:

| Step                        | Derivation                                                                                                                                            |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Look up the input schema    | Read `agreement.execution.inputs[inputId].data`.                                                                                                      |
| Encode each submitted field | `id = keccak256(stringToHex(fieldName))`; `fType =` the on-chain field type mapped from the authored variable type; `data =` ABI-encoded field value. |
| Encode the array            | ABI-encode the whole array as `(bytes32 id, uint8 fType, bytes data)[]`.                                                                              |

### Field type mapping

* `uint256` → `UINT256`
* `string` → `STRING`
* `address` → `ADDRESS`
* `bool` → `BOOL`
* `bytes32` → `BYTES32`
* `signature` → `STRING`
* `dateTime` → `STRING`
* `txHash` → `BYTES32`

### Field value encoding rules

* `STRING` → `abi.encode(string)`
* `ADDRESS` → `abi.encode(address)`
* `UINT256` → `abi.encode(uint256)`
* `BOOL` → `abi.encode(bool)`
* `BYTES32` → `abi.encode(bytes32)`

### Effective signing pipeline

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
fieldId = keccak256(stringToHex(fieldName))
encodedValue = abi.encode(valueForThatFieldType)
payload = abi.encode([{ id: fieldId, fType, data: encodedValue }, ...])
message = { inputId: inputIdBytes32, payload, nonce, deadline }
```

### Signing call

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const signatureHex = await walletClient.signTypedData({
  account,
  domain,
  types,
  primaryType: "PermitInput",
  message,
});
```

Then split `signatureHex` into:

* `r = 0x...`
* `s = 0x...`
* `v = 27` or `28`

If `inputId`, `values`, agreement schema, agreement address, signer nonce, chain, or deadline changes, the signature must be regenerated.

## Related pages

* [Architecture](/system-architecture/overview)
* [TypeScript client reference](/sdks/typescript-client)
* [Deploy an Agreement](/workflow/deploy-an-agreement)
* [Operate a Deployed Agreement](/workflow/operate-a-deployed-agreement)
* [Errors and troubleshooting](/reference/errors-and-troubleshooting)
