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).
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).