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

# Share a Simulator draft link

> Share a Simulator setup (one call or a bundle, with overrides and edited sources) as a link that prefills the form without running it.

A draft link opens the [Tenderly Simulator](/simulator-ui/overview) with the form prefilled: a single call or a multi-call [bundle](/simulator-ui/simulation-bundles), including [state overrides](/simulator-ui/state-overrides), funded balances, and [edited contract source](/simulator-ui/editing-contract-source). Nothing runs until the recipient clicks **Simulate**, so the link is safe to share in test reports, pull requests, governance proposals, or alerts.

A draft is stored server-side and referenced by id. The link itself carries no payload and no project:

`https://dashboard.tenderly.co/simulator/new?draftId=<id>`

## Share a draft from the dashboard

1. Build the simulation in the Simulator: pick the network, add one or more calls, set overrides, apply source edits.
2. Click **Share draft** in the page header. The draft is saved and the link is copied to your clipboard.

The link works for bundles and edited sources, and has no length limit. A draft shared from the dashboard is readable by members of the project it was saved in, and expires automatically.

<Frame caption="Share draft in the Simulator header saves the current setup and copies a link that reopens it.">
  <img src="https://mintcdn.com/tenderly/Rue4d2SVMkkw5nbs/images/simulator-ui/draft-share-button.webp?fit=max&auto=format&n=Rue4d2SVMkkw5nbs&q=85&s=089c42818cf86475a60aa8f6291f9805" alt="Simulator header with the Share draft button next to Simulate, and the tooltip explaining that the link works for bundles and edited sources" width="1394" height="145" data-path="images/simulator-ui/draft-share-button.webp" />
</Frame>

## What happens when a draft link opens

The dashboard makes one request for the draft and then, depending on the response:

* **Member of the draft's project.** The dashboard redirects into that project's Simulator with the form prefilled and no prompts.
* **Shared draft** (created through the API with `shared: true`). A **Select Project** dialog opens first; the draft opens in the project the recipient picks.
* **Invalid id, expired draft, or no access.** All three produce the same response so draft IDs cannot be probed. The recipient sees "This draft link is invalid, has expired, or you don't have access to it." and lands on the dashboard home.
* **Logged out.** The recipient signs in first, then the flow continues with the same link.
* **Unsaved edits in the Simulator.** Opening a draft replaces the current form, so the dashboard asks for confirmation before discarding the edits.

<Frame caption="A shared draft is not tied to a project, so the recipient picks where to open it.">
  <img src="https://mintcdn.com/tenderly/Rue4d2SVMkkw5nbs/images/simulator-ui/draft-select-project.webp?fit=max&auto=format&n=Rue4d2SVMkkw5nbs&q=85&s=1da80fd995d3da041f0aa4fc0e74e546" alt="Select Project dialog shown when opening a shared draft link, with a project dropdown" width="560" height="281" data-path="images/simulator-ui/draft-select-project.webp" />
</Frame>

### Partial restore

Each call in a draft is restored independently, so one problematic call does not reject the whole draft. A dismissible banner lists what did not survive, per call:

| Case | Result |
| - | - |
| The contract could not be loaded | The address is kept. Re-select the contract to retry. |
| The saved function is no longer on the contract ABI | The function select is emptied. Pick the function again. |
| The call carried an imported ABI | The ABI text is kept but not re-applied. Open **Edit ABI** and import it again. |
| The edited source failed to compile | The source stays on the call; the ABI falls back to the verified contract. Open **Edit source** and re-apply to retry. |
| A funded ERC-20 token's balance slot could not be resolved | The token stays on the form. Re-select it in **Fund address** to retry. |

No banner means everything was restored. A malformed payload, or a network that is not enabled on the project, rejects the draft wholesale: the recipient sees an error and an empty form.

<Frame caption="The restore banner names the calls that need attention after a draft opens.">
  <img src="https://mintcdn.com/tenderly/Rue4d2SVMkkw5nbs/images/simulator-ui/draft-partial-restore-banner.webp?fit=max&auto=format&n=Rue4d2SVMkkw5nbs&q=85&s=86464f8fe69e10d05f0144285d527b3f" alt="Simulator with a prefilled 3-call bundle and a warning banner listing the calls whose contract or function could not be restored" width="1600" height="1000" data-path="images/simulator-ui/draft-partial-restore-banner.webp" />
</Frame>

## Create a draft link from CI or tooling

Create the draft with the drafts API, then put the returned id in the link. Requests authenticate with an [access key](/platform/account/projects/api-tokens); the caller must be a member of the project in the URL.

`POST https://api.tenderly.co/api/v2/account/{account}/project/{project}/simulation-drafts` ([API reference](/api-reference/simulator/create-a-simulation-draft))

| Body field | Type | Required | Description |
| - | - | - | - |
| `payload` | object | yes | The [draft payload](#draft-payload). Stored as-is; the API does not validate its structure. A malformed payload is rejected when the link opens, not when the draft is created. |
| `shared` | boolean | no | `false` (default) scopes the draft to the project in the URL: only its members can open it, and they are redirected straight into it. `true` makes the draft openable by any signed-in Tenderly user, in a project of their choice. Use it for links aimed outside your organization, for example a governance UI that prefills a proposal simulation. |

The response is `{ "resource_id": "<uuid>" }`.

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
curl -X POST "https://api.tenderly.co/api/v2/account/$ACCOUNT/project/$PROJECT/simulation-drafts" \
  -H "X-Access-Key: $TENDERLY_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payload": {
      "v": 2,
      "network": { "id": "1" },
      "rows": [
        {
          "contractAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",
          "from": "0xab5801a7d398351b8be11c439e05c5b3259aec9b",
          "inputDataType": "raw",
          "rawFunctionInput": "0xa9059cbb…"
        }
      ]
    }
  }'
# {"resource_id":"3f2a6c1e-…"}
```

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import os, requests

payload = {
    "v": 2,
    "network": {"id": "1"},
    "rows": [
        {
            "contractAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",
            "from": "0xab5801a7d398351b8be11c439e05c5b3259aec9b",
            "inputDataType": "raw",
            "rawFunctionInput": "0xa9059cbb…",
        }
    ],
}

response = requests.post(
    f"https://api.tenderly.co/api/v2/account/{os.environ['ACCOUNT']}/project/{os.environ['PROJECT']}/simulation-drafts",
    headers={"X-Access-Key": os.environ["TENDERLY_ACCESS_KEY"]},
    json={"payload": payload},
)
response.raise_for_status()

url = f"https://dashboard.tenderly.co/simulator/new?draftId={response.json()['resource_id']}"
```

```javascript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// Node.js 18+
const payload = {
  v: 2,
  network: { id: "1" },
  rows: [
    {
      contractAddress: "0xdac17f958d2ee523a2206206994597c13d831ec7",
      from: "0xab5801a7d398351b8be11c439e05c5b3259aec9b",
      inputDataType: "raw",
      rawFunctionInput: "0xa9059cbb…",
    },
  ],
};

const response = await fetch(
  `https://api.tenderly.co/api/v2/account/${process.env.ACCOUNT}/project/${process.env.PROJECT}/simulation-drafts`,
  {
    method: "POST",
    headers: {
      "X-Access-Key": process.env.TENDERLY_ACCESS_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ payload }),
  },
);

const { resource_id } = await response.json();
const url = `https://dashboard.tenderly.co/simulator/new?draftId=${resource_id}`;
```

### Read a draft back

`GET https://api.tenderly.co/api/v2/account/{account}/project/{project}/simulation-drafts/{id}` ([API reference](/api-reference/simulator/get-a-simulation-draft))

The project in the URL is the caller's own project (any project they can view). The draft's visibility determines access:

* A project-scoped draft returns `{ "payload": {…}, "account_id": "…", "project_id": "…" }` when the caller can view the draft's origin project.
* A shared draft returns `{ "payload": {…} }` with no origin information.
* Anything else (unknown id, expired draft, no access to the origin project) returns `404` with `"slug": "resource_not_found"`.

The fastest way to get a correct payload for an advanced setup is to build it in the Simulator, click **Share draft**, and `GET` the draft by the id in the copied link. The response is a payload the dashboard accepts, including the `stateOverrides`, `customSource`, and `fundAddress` shapes.

## Draft payload

| Field | Type | Required | Description |
| - | - | - | - |
| `v` | number | yes | Schema version. Must be `2`. |
| `network` | `{ "id": string }` or `null` | yes | Chain id as a **string** (`"1"` is Mainnet, `"56"` is BNB Chain, and so on). Must be a network enabled on the project where the draft opens, otherwise the recipient gets a "network not available" error and an empty form. Always set it. `null` is accepted but skips the contract lookup and leaves the form mostly unusable. See [Supported Networks](/platform/supported-networks) for chain ids. |
| `rows` | array | yes | One object per call, in execution order. At least one row is required. See [Row fields](#row-fields). Session-level settings (block selection, `from`, L2 parameters) are taken from the first row. |

## Row fields

Only `contractAddress` is required. Omit anything you do not need. Omitted fields keep the form's defaults, and unknown extra fields are ignored.

### Contract and function

| Field | Type | Description |
| - | - | - |
| `contractAddress` | string | **Required.** The "to" address. The contract and its ABI are fetched on the recipient's side when the link opens. |
| `inputDataType` | `"decoded"` or `"raw"` | Selects the function-input mode. Use `"raw"` with `rawFunctionInput`, or `"decoded"` with `contractFunction` and `functionInputs`. |
| `rawFunctionInput` | string | Hex calldata (`0x…`) for raw mode. No ABI needed, which makes it the most reliable option for scripts. |
| `contractFunction` | object or `null` | Decoded-mode function reference: `{ "name": string, "selector"?: string, "signature"?: string }`. `name` is required inside the object. `selector` (4-byte hex, e.g. `"0xa9059cbb"`) is optional but preferred: it is matched exactly, which is safe for overloaded functions. Without it, matching falls back to `name` and may pick the wrong overload. `signature` is informational only. |
| `functionInputs` | array or object | Decoded-mode argument values. Preferred: a **positional array** in ABI order, for example `["0xabc…", "1000000", ["0xa…", "0xb…"]]`. Values are strings, numbers, or booleans. Array and tuple arguments may be passed natively as nested arrays or objects, with no pre-stringifying needed. The keyed form `{ "input_0": … }` is also accepted (it is what the dashboard emits). |
| `contractAbiImport` | string | Pre-fills the in-app **Edit ABI** field. It is not re-applied when the draft opens: function matching uses the fetched ABI (or the compiled edited source), so this field does not make decoded mode work for unverified contracts. Use raw calldata for those. |

### Transaction parameters

| Field | Type | Description |
| - | - | - |
| `from` | string | Sender address. |
| `gas` | string or number | Gas limit, e.g. `"8000000"`. `0x`-prefixed hex quantities are converted to decimal. |
| `gasPrice` | string or number | Gas price in wei. |
| `value` | string or number | Native-token value in wei. |

### Block selection

| Field | Type | Description |
| - | - | - |
| `block` | string or number | Block number to simulate at. Omit for the chain head. |
| `blockIndex` | string, number, or `null` | Transaction position inside the block. `null` or omitted means the start of the block. |
| `endOfBlock` | boolean | `true` runs the simulated transaction after every transaction in the block (overrides `blockIndex`). |
| `usePendingBlock` | boolean | `true` simulates on the pending block instead of a fixed number. |

See [Simulation Parameters](/simulator-ui/parameters#pending-vs-historical-block) for how block selection and transaction index behave in the UI.

### L2 parameters

These fields apply to OP-stack and Boba networks only.

| Field | Type | Description |
| - | - | - |
| `depositTx` | boolean | Mark as an L2 deposit transaction. |
| `mint` | string | Deposit mint amount. |

### Overrides

| Field | Type | Description |
| - | - | - |
| `blockHeaderOverrides` | object | `{ "number"?: …, "timestamp"?: … }`, each a string, number, or `null`. Overrides the simulated block header. |
| `stateOverrides` | array | Per-contract [state overrides](/simulator-ui/state-overrides): `{ "id"?: string, "contractAddress": string, "balance": string, "storage"?: [{ "key": string, "value": string }], "code"?: string }`. `id` is optional and generated when absent. `balance` is in wei (`""` means no balance override). `storage` keys and values are 32-byte hex. |
| `accessList` | array | [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list: `{ "address": string, "storageKeys": string[] }`. |

### Funded balances and edited source

| Field | Type | Description |
| - | - | - |
| `fundAddress` | object or `null` | The **Fund address** cheatcode: `{ "targetAddress": string, "tokens": [{ "tokenAddress": string, "amount": string }] }`. Use the zero address `0x0000000000000000000000000000000000000000` as `tokenAddress` to fund the native balance. For ERC-20 tokens the balance storage slot is resolved when the draft opens; if it cannot be resolved, the token stays on the form and the restore banner asks the recipient to re-select it. |
| `customSource` | object or `null` | An applied [source edit](/simulator-ui/editing-contract-source): `{ "compilerInfo": {…}, "customSourceData": [{ "name": string, "source": string, "path": string, "contractName"?: string, "address"?: string }] }`. `compilerInfo` is passed to the compiler as-is (compiler version, optimization settings, import remappings); take its shape from a draft shared from the dashboard. The source is compiled when the draft opens and its ABI takes over function matching for that call. |
| `contractSourceEdited` | boolean | Marks the call's source as edited. Defaults to `true` when `customSource` is present. |

## Validation and errors

Creating a draft:

| Response | Meaning |
| - | - |
| `400` `"Draft payload is required"` | `payload` is missing or `null`. |
| `400` `"Draft payload exceeds the 256KB limit"` | The serialized `payload` is larger than 256 KB. |
| `401` | Missing or invalid access key. |
| `404` | The caller is not a member of the project in the URL. |

Opening a draft is all-or-nothing at the payload level: a wrong `v`, a missing `rows` entry, a non-string `contractAddress`, or any wrong-typed row field rejects the **whole** draft with "This draft link is invalid or corrupted". Per-call problems that depend on live data (contract fetch, function match, source compile, token slot) degrade individually as described in [Partial restore](#partial-restore).

## Limits

* Payloads are capped at **256 KB** per draft, enforced by the API.
* Drafts expire automatically. An expired draft returns `404` from the API and the "invalid, has expired, or you don't have access" message in the dashboard.
* Opening a draft link requires a Tenderly account. Shared drafts (`shared: true`) are readable by any signed-in user; project-scoped drafts only by members of the origin project.
* The contract ABI is fetched when the link opens. If it cannot be resolved (an unverified contract), decoded-mode calls open with the address filled but no function selected. Raw mode is immune to this.

## Legacy `?draft=` links

Before server-side drafts, the dashboard encoded a single call directly into the URL:

`https://dashboard.tenderly.co/<org>/<project>/simulator/new?draft=<value>`

`<value>` is a version-1 payload (`{ "v": 1, "network": {…}, "row": {…} }`, one `row` instead of `rows`) as UTF-8 JSON, then **base64url** (`+` to `-`, `/` to `_`, `=` padding stripped). Existing links keep opening. The `row` object accepts the [row fields](#row-fields) above except `fundAddress`, `customSource`, and `contractSourceEdited`, and the whole URL must stay under about 2000 characters. The dashboard no longer produces this format; use the drafts API for new integrations.

For the form fields a draft populates, see [Simulation Parameters](/simulator-ui/parameters). To keep a modified state as a persistent environment instead of a one-shot simulation, use [Virtual Environments](/virtual-environments/overview).


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