Skip to main content

Non-Custodial Deploy (Connect)

urblock is non-custodial: you hold your signing key, and the platform only relays the UserOperations you sign. Every tenant gets an ERC-4337 smart account ("connect account"); on-chain actions are signed by you and relayed through the urblock bundler. The platform never sees your private key.

When you create a connect account, urblock automatically provisions it on-chain (deploys the account, authorizes it on the token factory, and sponsors its testnet gas), so you can deploy immediately.

1. Sign up + create an API key

Register a tenant, then create a secret key (see Quickstart). New tenants default to connect (non-custodial) mode.

2. Generate your signer

You generate and keep this key — it owns your connect account.

import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";

const privateKey = generatePrivateKey(); // store this securely (like an API secret)
const account = privateKeyToAccount(privateKey);
const signer = {
address: account.address,
signDigest: (hash: string) => account.sign({ hash: hash as `0x${string}` }),
};

3. Register your connect account

import { Urblock } from "@urblock/sdk";
import { keccak256, toBytes } from "viem";

const urblock = new Urblock(process.env.URBLOCK_API_KEY!);
const CHAIN_ID = 11155111; // Sepolia

await urblock.connect.createAccount({
signer_id: keccak256(toBytes(account.address)),
key_data: account.address,
salt: "0x" + "00".repeat(32),
chain_id: CHAIN_ID,
signer_type: 0, // ECDSA / EOA
});

The platform pre-deploys the account and authorizes it on the token factory. You fund its gas (urblock is non-custodial — it never spends from your balance):

  • Testnet (Sepolia): request ETH from the faucet to your connect account.
    await urblock.faucet.drip({ network: "sepolia", wallet_address: account.address });
  • Mainnet: send ETH to your connect account address (the address returned by createAccount). The account pays its own UserOperation gas.

If you skip funding, deploys return `402 insufficient_gas` with instructions.

4. Deploy a token — you sign, platform relays

const token = await urblock.tokens.create({
name: "My Token",
symbol: "MTK",
standard: "ERC20",
network: "sepolia",
decimals: 18,
initial_supply: "1000000000000000000000",
features: { mintable: true },
});

// Sign the pending UserOp(s) with YOUR key and relay them (non-custodial).
await urblock.connect.fulfillPendingOps(signer, { chainId: CHAIN_ID });

// Poll until deployed.
let t = await urblock.tokens.retrieve(token.id);
while (["pending", "submitted", "deploying", "awaiting_sign"].includes(t.status)) {
await new Promise((r) => setTimeout(r, 5000));
t = await urblock.tokens.retrieve(token.id);
}
console.log("deployed:", t.contract_address);

That is the whole flow. Mint/transfer/burn and every other on-chain action use the same pattern: call the API, then fulfillPendingOps(signer).

Custodial mode is deprecated

Earlier versions had a custodial mode where the platform held keys. All tenants are now connect (non-custodial). If you see `no_connect_account` on deploy, create a connect account first (step 3).