> For the complete documentation index, see [llms.txt](https://docs.bloxchain.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bloxchain.app/developers/sdk-setup/sdk-core-patterns.md).

# Core patterns

Read state, drive the three execution paths, interpret statuses and handle errors with `@bloxchain/sdk`.

**You need:** [SDK setup](/developers/sdk-setup.md) complete, an account address on Sepolia

## Read state

Use a `PublicClient` and the read methods of each wrapper. All wrappers point at the same account address.

| Question                                                    | Call                                                                                                    |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Who owns the account, who can recover it, who may broadcast | `SecureOwnable.owner()`, `getRecovery()`, `getBroadcasters()`                                           |
| How long is the time lock                                   | `SecureOwnable.getTimeLockPeriodSec()`                                                                  |
| Which roles exist and who holds them                        | `RuntimeRBAC.getSupportedRoles()`, `getRole(hash)`, `getWalletRoles(address)`, `hasRole(hash, address)` |
| What an action's schema looks like                          | `RuntimeRBAC.getFunctionSchema(selector)`                                                               |
| Which requests are open                                     | `BaseStateMachine.getPendingTransactions()`, `getTransaction(txId)`                                     |

Details and signatures: [API reference](/protocol-reference/api-reference.md).

## The three execution paths

The Platform names three execution paths. Each maps to Protocol calls and permission steps.

| Platform name       | Tab label | Protocol shape                                                          | Time lock enforced?                     | Calls                                                                                                                                                 |
| ------------------- | --------- | ----------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Delayed release** | Delayed   | Time-locked request, then on-chain approve                              | Yes                                     | Request: `GuardController.executeWithTimeLock(...)`. After `releaseTime`: `approveTimeLockExecution(txId)`. Cancel during the wait is also on chain.  |
| **Signed approval** | Signed    | Request on chain, approval signed off chain, submitted by a broadcaster | No (the signature is the authorisation) | Approver: `MetaTransactionSigner.createSignedMetaTransactionForExisting(...)`. Broadcaster submits the signed approval.                               |
| **One signature**   | One sign  | Request and approval in one signature, submitted by a broadcaster       | No                                      | Requester: `MetaTransactionSigner.createUnsignedMetaTransactionForNew(...)`, sign. Broadcaster: `GuardController.requestAndApproveExecution(metaTx)`. |

### The nine action steps

Permissions are granted per step, not per path. There are nine steps, and each is a `TxAction` value in the SDK. Each step belongs to one of three phases: **Request**, **Approval**, **Execute**.

| Step                      | `TxAction`                         | Phase    |
| ------------------------- | ---------------------------------- | -------- |
| Pending Request           | `EXECUTE_TIME_DELAY_REQUEST`       | Request  |
| Approve                   | `EXECUTE_TIME_DELAY_APPROVE`       | Approval |
| Cancel                    | `EXECUTE_TIME_DELAY_CANCEL`        | Approval |
| Sign and approve request  | `SIGN_META_REQUEST_AND_APPROVE`    | Request  |
| Sign approval             | `SIGN_META_APPROVE`                | Approval |
| Sign cancellation         | `SIGN_META_CANCEL`                 | Approval |
| Finalize approved request | `EXECUTE_META_REQUEST_AND_APPROVE` | Execute  |
| Finalize approval         | `EXECUTE_META_APPROVE`             | Execute  |
| Finalize cancellation     | `EXECUTE_META_CANCEL`              | Execute  |

A role needs the step for the phase it performs, **and** the matching gate on the handler selector that carries the step. Granting a step without its gate is the most common reason a correctly assigned wallet still cannot act. To grant either from code, see [Configure an account programmatically](/developers/sdk-setup/configure-programmatically.md). See also [How governed operations work](/introduction/how-it-works.md).

### Delayed release

1. **Request.** A wallet with request permission calls `executeWithTimeLock(target, value, selector, params, gasLimit, operationType, { from })`. A record is created with status `PENDING` and `releaseTime = now + timeLockPeriodSec`.
2. **Wait.** Approving before `releaseTime` reverts.
3. **Approve.** A wallet with approve permission calls `approveTimeLockExecution(txId, { from })`. The record moves to `EXECUTING` and the guarded call runs in the same transaction.
4. **Record.** The record ends `COMPLETED` or `FAILED`. Cancelling a pending record ends it `CANCELLED`.

Always wait for the receipt before reading state.

### Signed approval and One signature

1. Build the unsigned meta-transaction with `MetaTransactionSigner`. The contract itself generates the EIP-712 digest, so the signed payload always matches what the chain verifies.
2. Sign the **raw digest**, with no EIP-191 wrapper. The contract recovers the signer with a plain `ecrecover` over the digest itself, so any prefix breaks recovery. Use the signer's `signMetaTransactionWithWallet(unsigned)` for a browser or hardware wallet, which signs the digest as EIP-712 typed data, or `account.sign({ hash: unsigned.message })` when you hold the key in process. Do not reach for viem's `signMessage`, including its `{ raw }` form: it always prepends `\x19Ethereum Signed Message:\n` before hashing, which is exactly the prefix the contract cannot accept.
3. Set a short **deadline** and use the signer's current nonce (`getSignerNonce`). Nonces are per signer and sequential, so one signer's meta-transactions must be submitted in order.
4. A **Broadcaster** submits the signed payload and pays gas. The handler selector in the payload must match the function actually used to submit.

Signed payloads can also leave your system as a `.bloxmeta.json` file and be imported into the Platform. See [Signed files](/platform-guide/operate/signed-files.md) and [Meta-transactions](/protocol-reference/api-reference/meta-transactions.md).

## Statuses

Protocol statuses on the record: `UNDEFINED`, `PENDING`, `EXECUTING`, `COMPLETED`, `FAILED`, `CANCELLED`, plus `PROCESSING_PAYMENT` when an attached payment runs. There is no `APPROVED` status; approval moves a record straight from `PENDING` to `EXECUTING`.

The Platform layers user-facing labels on top of these. In **Approvals**, a `PENDING` record shows as **Awaiting Approval** or **Awaiting Your Approval**, with a **State** of **Blocked** while the time lock runs and **Ready** once it has passed. A signed but unsubmitted payload sits in the **Approved** tab as **Ready to Execute**, or **Ready to Submit Cancellation** for a signed cancellation, and becomes **Expired** once its deadline passes. While a submission confirms, rows read **Submitting**, **Pending Confirmation**, **Executing** and then **Confirmed**. Settled records show in **History** as **Executed**, **Cancelled** or **Failed**. See [Approvals](/platform-guide/operate/approvals.md) and the [Glossary](/resources/glossary.md).

## Errors

* SDK errors extend `BloxchainError` (`code`, `details`); contract calls raise `ContractError` (`contractAddress`, `method`); bad inputs raise `ValidationError`.
* A `FAILED` record means the guarded call reverted after approval. Read the revert data from the execution transaction.
* Most reverts before execution are policy denials: missing role, missing gate, target or recipient not on an allowlist, or an approval attempted before `releaseTime`. Fix the policy, do not retry blindly.

## Retries

* Reads and simulations are safe to retry.
* For writes, re-simulate after a failure. Re-sending a request creates a second `PENDING` record, which is rarely what you want.
* Respect the time lock: poll `getTransaction(txId).releaseTime` and approve only after it passes.
* For meta-transactions, a resubmitted payload with a consumed nonce or an expired deadline will revert. Sign a fresh one.

## Worked examples

Short snippets verified against the Protocol reference, for a smart account on Sepolia. Install and client setup are covered once in [SDK setup and authentication](/developers/sdk-setup.md); these snippets assume `publicClient`, `walletClient`, `account` and `accountAddress` from that page, and `RPC_URL`, `ACCOUNT_ADDRESS` and (for writes) `PRIVATE_KEY` in your environment. Fuller samples: [Basic examples](/protocol-reference/getting-started/examples-basic.md) and [Protocol getting started](/protocol-reference/getting-started.md).

### Delayed release: request, wait, approve

A time-locked ownership transfer is the simplest end-to-end delayed request. The requester must be the owner or recovery address; approval is possible only after the time lock.

```typescript
import { SecureOwnable, BaseStateMachine } from '@bloxchain/sdk';
import { sepolia } from 'viem/chains';

const ownable = new SecureOwnable(publicClient, walletClient, accountAddress, sepolia);
const machine = new BaseStateMachine(publicClient, walletClient, accountAddress, sepolia);

// 1. Request
const req = await ownable.transferOwnershipRequest({ from: account.address });
await publicClient.waitForTransactionReceipt({ hash: req.hash });

// 2. Wait until releaseTime has passed, then find the pending id
const [txId] = await machine.getPendingTransactions();

// 3. Approve (executes in the same transaction)
const approve = await ownable.transferOwnershipDelayedApproval(txId, { from: account.address });
await publicClient.waitForTransactionReceipt({ hash: approve.hash });
```

### Delayed release: a guarded call to an allowlisted contract

For registered actions, the request goes through the guard. The target must be on the account's **Allowed contracts** list for that function.

```typescript
import { GuardController, EngineBlox } from '@bloxchain/sdk';

const guard = new GuardController(publicClient, walletClient, accountAddress, sepolia);

const target = '0x...' as `0x${string}`;                 // allowlisted contract
const selector = '0xa9059cbb' as `0x${string}`;          // transfer(address,uint256)
const params = '0x...' as `0x${string}`;                 // ABI-encoded arguments

const request = await guard.executeWithTimeLock(
  target,
  0n,                                   // value
  selector,
  params,
  300_000n,                             // gas limit for the guarded call
  EngineBlox.NATIVE_TRANSFER_OPERATION, // or your custom operation type
  { from: account.address },
);
await publicClient.waitForTransactionReceipt({ hash: request.hash });

// later, after the time lock, from an approver wallet:
// await guard.approveTimeLockExecution(txId, { from: approver });
```

### Signed approval: sign off chain, submit with a broadcaster

The approver signs; a Broadcaster submits and pays gas. Sign the raw digest that the contract produced.

```typescript
import { MetaTransactionSigner } from '@bloxchain/sdk';

// Approver side, browser or hardware wallet
const signer = new MetaTransactionSigner(publicClient, walletClient, accountAddress, sepolia);
const unsigned = await signer.createUnsignedMetaTransactionForExisting(txId, metaTxParams);
const signed = await signer.signMetaTransactionWithWallet(unsigned);

// Broadcaster side: submit `signed` through the matching handler before its deadline.
```

When the key is in process rather than in a wallet, sign the digest directly and attach the signature yourself:

```typescript
const unsigned = await signer.createUnsignedMetaTransactionForExisting(txId, metaTxParams);
const signature = await account.sign({ hash: unsigned.message });
const signed = await signer.createSignedMetaTransactionWithSignature(unsigned, signature);
```

`txId` is the pending record you are approving; `metaTxParams` (including the signer's current nonce and a deadline) is described in [Meta-transactions](/protocol-reference/api-reference/meta-transactions.md). The same signed payload, saved as a `.bloxmeta.json` file, can be imported into the Platform under **Approvals → Import signed file**. See [Signed files](/platform-guide/operate/signed-files.md).

### Configuration batches

Registering an action, creating a role, granting a gate or adding a destination to an allowlist all travel as signed configuration batches that a Broadcaster submits. The batch shape, a worked allowlist example and the idempotency rules are on [Configure an account programmatically](/developers/sdk-setup/configure-programmatically.md).

### Wait for and check a transaction

```typescript
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') {
  throw new Error(`Transaction reverted: ${hash}`);
}
```

A successful receipt on an approval does not by itself mean the guarded call succeeded. Read the record: `COMPLETED` means it ran, `FAILED` means it reverted inside the guard.

### Runnable references

The Protocol repository ships SDK sanity suites under `scripts/sanity-sdk/` (run with `npm run test:sanity-sdk`) that exercise SecureOwnable, RuntimeRBAC and GuardController end to end. Browse them at the pinned commit: [scripts/sanity-sdk](https://github.com/PracticalParticle/Bloxchain-Protocol/tree/99beac2d6e6d7567c23b25cecaf6f4053f31c987/scripts/sanity-sdk).

## Version alignment

`@bloxchain/sdk`, `@bloxchain/contracts` and the deployed bytecode must come from the same release. See [Releases and compatibility](/resources/releases.md).

## Related

* [Protocol best practices](/protocol-reference/getting-started/best-practices.md)
* [State machine engine](/protocol-reference/bloxchain-architecture/state-machine-engine.md)
* [Guard controller](/protocol-reference/api-reference/guard-controller.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.bloxchain.app/developers/sdk-setup/sdk-core-patterns.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
