Skip to content

Latest commit

 

History

History
224 lines (158 loc) · 9.07 KB

File metadata and controls

224 lines (158 loc) · 9.07 KB

Getting Started with Bloxchain – Account Pattern

This guide shows the simplest way to start using the Bloxchain protocol: by connecting to an Account‑based contract that already combines all core components (SecureOwnable, RuntimeRBAC, GuardController) behind a single address.

For a deeper explanation of the pattern itself, see the Account Pattern doc.

📋 Prerequisites

  • Node.js 18+
  • TypeScript 4.5+
  • npm or yarn
  • Basic knowledge of Ethereum and smart contracts

🚀 Installation

npm install @bloxchain/sdk

# Or with yarn
yarn add @bloxchain/sdk

🔧 Basic Setup (Account-Based Contract)

1. Import Required Dependencies

import {
  SecureOwnable,
  RuntimeRBAC,
  GuardController,
} from '@bloxchain/sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { sepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

2. Initialize Clients

const rpcUrl = process.env.RPC_URL!;           // e.g. https://sepolia.infura.io/v3/...
const privateKey = process.env.PRIVATE_KEY!;   // never hardcode; use env vars

const account = privateKeyToAccount(privateKey);

// Public client for reads
const publicClient = createPublicClient({
  chain: sepolia,
  transport: http(rpcUrl),
});

// Wallet client for writes
const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http(rpcUrl),
});

3. Connect to an Account-Based Contract

Use a deployed Account implementation (for example AccountBlox) from deployed-addresses.json:

// Example shape – adjust to your deployed-addresses.json
import deployed from '../../deployed-addresses.json';

const network = 'sepolia' as const;
const accountAddress = deployed[network].AccountBlox.address as `0x${string}`;

// All three wrappers point to the SAME address
const secureOwnable = new SecureOwnable(publicClient, walletClient, accountAddress, sepolia);
const runtimeRBAC = new RuntimeRBAC(publicClient, walletClient, accountAddress, sepolia);
const guardController = new GuardController(publicClient, walletClient, accountAddress, sepolia);

📖 Common Tasks with an Account

1. Inspect Ownership & Security State

// SecureOwnable – core security state
const owner = await secureOwnable.owner();
const broadcasters = await secureOwnable.getBroadcasters();
const recovery = await secureOwnable.getRecovery();
const timeLockPeriod = await secureOwnable.getTimeLockPeriodSec();

console.log({ owner, broadcasters, recovery, timeLockPeriod });

// RuntimeRBAC – roles and permissions
const supportedRoles = await runtimeRBAC.getSupportedRoles();
const firstRole = supportedRoles[0];
const roleInfo = await runtimeRBAC.getRole(firstRole);

console.log('First role info:', roleInfo);

2. Perform a Secure Ownership Transfer

// 1) Owner (or recovery) requests a transfer (new owner is encoded in the state machine)
const txRequest = await secureOwnable.transferOwnershipRequest({
  from: account.address,
});

await publicClient.waitForTransactionReceipt({ hash: txRequest.hash });

// 2) After the timelock expires, approve the pending transaction (txId from BaseStateMachine.getPendingTransactions / getTransaction)
const baseStateMachine = new BaseStateMachine(publicClient, walletClient, accountAddress, sepolia);
const pendingTxIds = await baseStateMachine.getPendingTransactions();
const txId = pendingTxIds[0];

const txApprove = await secureOwnable.transferOwnershipDelayedApproval(txId, {
  from: account.address,
});

await publicClient.waitForTransactionReceipt({ hash: txApprove.hash });

3. Guarded Call via GuardController

Use the GuardController wrapper to execute a time‑locked call to a whitelisted target:

import { EngineBlox } from '@bloxchain/sdk/lib/EngineBlox';

// Assume target is a whitelisted contract for a registered function selector
const target = '0x...'; // e.g. ERC20 token
const functionSelector = '0xa9059cbb' as `0x${string}`; // transfer(address,uint256)
const params = '0x...' as `0x${string}`; // abi-encoded params

const gasLimit = 300_000n;
const operationType = EngineBlox.NATIVE_TRANSFER_OPERATION; // or custom op type

const txResult = await guardController.executeWithTimeLock(
  target,
  0n,                    // value
  functionSelector,
  params,
  gasLimit,
  operationType,
  { from: account.address },
);

console.log('Requested guarded execution tx hash:', txResult.hash);

(Approvals, cancellations, and meta‑tx flows use the same patterns as described in the component‑specific docs.)


Deployment and initialization

Account‑style contracts use OpenZeppelin Initializable semantics: there is no constructor state on the implementation; a single correct initialize(...) (or your product’s chained initializer) must run on the proxy (or minimal proxy). Treat initialization as part of deployment: the address end users call should not appear as a public, uninitialized proxy across block boundaries—wire initialize in the same transaction that creates the proxy (factory, proxy constructor _data, or equivalent), then rely on ownership, RBAC, and guards.

1. Recommended: factory / cloner pattern

To avoid “forgot to call initialize” or wrong ordering when spinning up many instances, prefer a factory that creates the proxy and calls initialize in the same transaction. The repo includes CopyBlox as a reference pattern (contracts/examples/applications/CopyBlox/CopyBlox.sol):

  • Validates the implementation implements IBaseStateMachine.
  • Clones.clone (EIP‑1167) then calls initialize(address,address,address,uint256,address) on the new clone.
  • If initialization reverts, the whole transaction reverts—you do not end up with a live, uninitialized clone from that path.

Use the same initializer arity and argument order your concrete contract exposes (often the same five parameters as CopyBlox / BaseStateMachine).

2. Proxy deploy runbook (atomic initialize)

If you deploy transparent / UUPS / ERC‑1967–style proxies yourself, you still must avoid a live, public proxy that is uninitialized between transactions. Prefer one atomic transaction that both creates the proxy and runs initialize—for example:

  • OpenZeppelin proxy constructors that accept _data: supply ABI‑encoded initialize(...) calldata so the proxy’s constructor performs the initializer delegatecall before the deployment transaction ends.
  • A proxy factory (or deployer helper) whose single entrypoint deploys the proxy and calls initialize on the new address in the same transaction (any revert aborts the whole deploy; no orphan uninitialized proxy from that path).

Explicit runbook:

  1. Deploy implementation (never call user‑facing initialize on the implementation in production unless you mean to brick or document a pattern—follow OZ guidance).
  2. Create the proxy using a pattern where initialize runs in the same transaction as proxy creation, with owner, broadcaster, recovery, timelock, and eventForwarder (match your concrete contract’s arity and order). Do not rely on a follow‑up transaction to initialize a proxy that is already callable at its deployed address.
  3. Only after that atomic creation+initialization transaction succeeds, run smoke‑reads on the proxy: owner(), getRecovery(), getTimeLockPeriodSec(), and verify eventForwarder matches your intent—before funding, granting roles, or sending production traffic.

More detail: Best Practices — Deployment (initializer subsection under Deployment) and Account Pattern.


🔒 Security Basics

Keep these minimum practices in your integration:

// 1) Environment-based secrets
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) throw new Error('PRIVATE_KEY env var is required');

// 2) Simple address validation
const isAddress = (value: string) => /^0x[a-fA-F0-9]{40}$/.test(value);

// 3) Error handling around writes
try {
  const result = await secureOwnable.transferOwnershipRequest({ from: account.address });
  console.log('Tx hash:', result.hash);
} catch (error) {
  console.error('Tx failed:', error);
}

For a full set of recommendations, see Best Practices.


📚 Next Steps

  1. Learn more about the Account Pattern and how it composes the core components.
  2. Explore component‑level docs:
  3. Dive into architecture:
  4. Look at end‑to‑end flows in Basic Examples.

For detailed API signatures, see the API Reference.