Skip to main content
A Chipi wallet is self-custodial: only you can authorize transactions, and Chipi holds no key. For users who want full sovereignty, the wallet can export the owner private key (Security → Self-custody → Export private key). This page is for the developer who exported it and wants to use it from code — e.g. a script or backend — while still using the app normally.
The exported key can move all your funds. Anyone who gets it can drain your wallet, and no one (not even Chipi) can recover or revoke it. Never paste it into a website, never share it, never commit it to a repo. Treat it like cash.

The one thing to know first

A Chipi wallet is a SHHH smart account (account abstraction), not a plain EOA like a MetaMask account. Its __validate__ is disabled on purpose (shhh-wallet-cairo:src/account.cairo panics with 'SHHH: __validate__ disabled'), so the standard starknet.js path throws:
// ❌ Throws `SHHH: __validate__ disabled` — direct invokes are turned off.
const acct = new Account(provider, MY_ADDRESS, MY_PRIVATE_KEY);
await acct.execute(calls);
Instead you sign an OutsideExecution and submit it through execute_from_outside_v2. Your exported key signs; the transaction can be paid by anyone (you or a relayer), because the OE is permissionless.

Option A — Self-sovereign (no Chipi servers, you pay gas)

You sign with your exported key, then submit the signed OE from any funded Starknet account you control. Chipi is not in the loop — this is what makes self-custody verifiable. In TypeScript, the ShhhAccount adapter wraps the whole flow so it reads like starknet.js. In Python, compose the same primitives directly.
import { Account, RpcProvider, CallData } from "starknet";
import { ShhhAccount, submitViaAccount } from "@chipi-stack/backend";

const provider = new RpcProvider({ nodeUrl: RPC });

// A funded account that only pays gas — it never sees your owner key.
const relayer = new Account(provider, RELAYER_ADDRESS, RELAYER_PRIVATE_KEY);

const account = new ShhhAccount(provider, SHHH_ADDRESS, EXPORTED_PRIVATE_KEY, {
  submit: submitViaAccount(relayer),
});

// Example: send 1 USDC (native USDC, 6 decimals).
const USDC = "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb";
const calls = [
  { contractAddress: USDC, entrypoint: "transfer", calldata: CallData.compile([RECIPIENT, "1000000", "0"]) },
];

const { transactionHash } = await account.execute(calls);
await provider.waitForTransaction(transactionHash);

Option B — Gasless, via Chipi’s paymaster

Same signing, but hand the serialized calldata to Chipi’s relayer so you don’t fund a gas account. You still hold the key; Chipi only relays.
// After producing `calldata` (above):
const res = await fetch("https://api.chipipay.com/v1/transactions/execute-sponsored-raw", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "<your pk_ key>",
    "Authorization": "Bearer <your JWT>",
  },
  body: JSON.stringify({ accountAddress: SHHH_ADDRESS, calldata, signerKind: "STARK" }),
});
const { transactionHash } = await res.json();

Notes

  • ownerIndex 0 is your primary owner (the exported key). Devices you added later are other owners with their own non-extractable passkeys; exporting doesn’t touch them.
  • Additive, not destructive. Using the key from code doesn’t disable the app — you can keep using Face ID in the wallet and your key in code at the same time, like MetaMask.
  • Mainnet only. Use the mainnet chain id and native USDC (0x033068…b35fb, not USDC.e).
  • Replay protection is per-nonce; reusing a nonce reverts with SRC9: duplicate nonce. The ShhhAccount adapter generates a fresh nonce per call.
  • If you ever suspect the key leaked, move your funds to a fresh wallet — there is no revoke for a raw key.