AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Openfort Backend Wallets

skill-openfort-xyz-agent-skills-backend-wallet · by openfort-xyz

>

No reviews yet
0 installs
20 views
0.0% view→install

Install

$ agentstack add skill-openfort-xyz-agent-skills-backend-wallet

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-openfort-xyz-agent-skills-backend-wallet)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Openfort Backend Wallets? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Openfort Backend Wallets (Developer Custody)

> ⚠️ Test keys vs. live keys — read before creating any wallet. > Openfort runs two fully isolated universes: test mode (sk_test_… / pk_test_…, testnet only) and live mode (sk_live_… / pk_live_…, mainnet, real funds). Objects created in one universe cannot be read, signed with, or recovered from the other. > - A wallet created with a test (dev) secret key exists only on testnet. It is not usable in production and must never custody or move real funds. > - To go to production, switch to your live secret key and create fresh wallets — never migrate or reuse test wallets across modes. > - Secret keys (sk_…) are server-side only. Never commit them or expose them to a client. > See https://openfort.io/docs/configuration/api-keys.

Backend wallets are server-controlled EOAs for automated blockchain operations — no user interaction required. Private keys are stored in hardware-backed secure enclaves and never leave the secure environment.

When to use backend wallets (vs embedded wallets):

  • Server-side automation: treasury ops, batch minting, payroll, airdrops
  • AI agent wallets: autonomous trading, payment processing
  • Programmatic signing: no browser or user present
  • Cross-border payments: automated stablecoin disbursement

When to use embedded wallets instead:

  • User-facing wallets where the user controls the key
  • Browser/mobile signing flows with user approval

Setup

npm install @openfort/openfort-node
# EVM peer dependency (required for sendTransaction):
npm install viem
# Solana peer dependencies (required for Solana operations):
npm install @solana/kit @solana-program/system @solana-program/compute-budget @solana-program/token @solana/kora @solana/transaction-confirmation

Environment Variables

OPENFORT_SECRET_KEY=sk_test_...            # Secret API key (required)
OPENFORT_WALLET_SECRET=...              # Base64-encoded EC P-256 private key (required for mutations)
OPENFORT_PUBLISHABLE_KEY=pk_test_...    # Required for Solana operations (Kora gasless)
OPENFORT_BASE_URL=https://api.openfort.io  # Optional, defaults to production

Initialize

import Openfort from '@openfort/openfort-node'

const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY!, {
  walletSecret: process.env.OPENFORT_WALLET_SECRET!,
  publishableKey: process.env.OPENFORT_PUBLISHABLE_KEY,
})

// Or use env vars directly (auto-detected):
// OPENFORT_SECRET_KEY, OPENFORT_WALLET_SECRET, OPENFORT_PUBLISHABLE_KEY
const openfort = new Openfort()

Authentication Model

All mutating backend wallet requests (POST, DELETE, PUT on /accounts/backend/*) are authenticated with two layers:

  1. API Key — Bearer token in Authorization header (sk_test_... or sk_live_...)
  2. Wallet Auth (X-Wallet-Auth) — Signed with the wallet secret. The SDK generates this automatically.

The SDK handles auth generation transparently — just provide walletSecret at init.

> Important: Wallet-auth requests are not retried on failure. All other requests use automatic retry with exponential backoff.


EVM Backend Wallets

Create

const account = await openfort.accounts.evm.backend.create({
  wallet: 'pla_...', // Optional — associates the wallet with a player
})
// account.id       — 'acc_...'
// account.address  — '0x...' (viem Address type)
// account.walletId — 'wal_...'
// account.custody  — 'Developer'

List & Get

// List all EVM backend wallets (paginated)
const { accounts, total, nextPageToken } = await openfort.accounts.evm.backend.list({
  limit: 50,  // 1-100, default 10, optional
  skip: 0,    // optional
})

// Get by ID or address
const account = await openfort.accounts.evm.backend.get({ id: 'acc_...' })
// OR
const account = await openfort.accounts.evm.backend.get({ address: '0x...' })
// Throws AccountNotFoundError if not found

Send Transaction (EVM — Gasless with EIP-7702)

sendTransaction handles the full EIP-7702 delegation + gasless flow automatically:

  1. First call on a chain: registers EIP-7702 delegation on-chain (upgrades EOA to smart account), then sends transaction
  2. Subsequent calls: skips delegation, sends directly
  3. Multiple interactions are batched atomically in a single transaction (enabled by smart account delegation)
const account = await openfort.accounts.evm.backend.create()

const result = await openfort.accounts.evm.backend.sendTransaction({
  account: account,           // Required — account object from create() or get()
  chainId: 84532,             // Required — target chain ID (resolved via viem/chains)
  interactions: [              // Required — array of contract calls (batched atomically)
    {
      to: '0xRecipientAddress',  // Required — destination address
      value: '0',                // Optional, default '0' — Wei amount as string
      data: '0x',                // Optional, default '0x' — calldata
    },
    // Add more interactions for atomic batching
  ],
  policy: 'pol_...',              // Optional — fee sponsorship ID for gasless tx
  rpcUrl: 'https://sepolia.base.org',  // Optional — custom RPC (required for chains not in viem/chains)
})

console.log('TX Hash:', result.response?.transactionHash)
console.log('Status:', result.response?.status)
console.log('Gas Used:', result.response?.gasUsed)

> Note: If chainId is not found in viem/chains and no rpcUrl is provided, a DelegationError is thrown.

Sign Data

The account object supports multiple signing methods:

const account = await openfort.accounts.evm.backend.get({ address: '0x...' })

// Sign a raw hash (32-byte hex)
const sig1 = await account.sign({ hash: '0xabcdef...' })

// Sign a human-readable message (EIP-191 personal_sign)
const sig2 = await account.signMessage({ message: 'Hello World' })

// Sign a serializable transaction
const sig3 = await account.signTransaction({
  to: '0x...',
  value: 100n,
  chainId: 84532,
})

// Sign EIP-712 typed data
const sig4 = await account.signTypedData({
  domain: { name: 'MyApp', version: '1', chainId: 84532 },
  types: { Transfer: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }] },
  primaryType: 'Transfer',
  message: { to: '0x...', amount: 100n },
})

Or use the lower-level API directly:

const signature = await openfort.accounts.evm.backend.sign({
  id: account.id,
  data: '0x...',  // hex-encoded data to sign
})

Update to Delegated Account (EIP-7702)

Manually register EIP-7702 delegation without sending a transaction:

const delegatedAccount = await openfort.accounts.evm.backend.update({
  walletId: account.walletId,
  accountType: 'Delegated Account', // Required for EIP-7702 upgrade
  chainId: 84532,
  implementationType: 'Calibur',
  accountId: account.id,
})

Import Private Key

The SDK handles E2E encryption internally — just provide the raw private key:

const imported = await openfort.accounts.evm.backend.import({
  privateKey: '0xYourPrivateKeyHex', // hex string (with or without 0x prefix)
})
// imported.id, imported.address

> Under the hood: The SDK encrypts your private key with RSA-OAEP (SHA-256) using the server's public key before transit. The server holds the corresponding private key in a KMS HSM (non-extractable).

Low-level encryption helpers (advanced)

For manual encryption workflows (e.g., custom import pipelines), the SDK also exports:

import {
  generateRSAKeyPair,
  encryptForImport,
  decryptExportedPrivateKey,
  IMPORT_ENCRYPTION_PUBLIC_KEY,
} from '@openfort/openfort-node'

// These are synchronous functions:
const keyPair = generateRSAKeyPair()              // Returns { publicKey, privateKeyPem }
const encrypted = encryptForImport('0xKey', IMPORT_ENCRYPTION_PUBLIC_KEY) // Returns base64 string
const decrypted = decryptExportedPrivateKey(encryptedBase64, keyPair.privateKeyPem) // Returns hex string

Export Private Key

The SDK handles E2E decryption internally — returns the private key directly:

const privateKey = await openfort.accounts.evm.backend.export({
  id: account.id,
})
// privateKey is hex string (no 0x prefix)

Delete

await openfort.accounts.evm.backend.delete(account.id)
// Permanently deletes wallet and private key — irreversible

Solana Backend Wallets

> All Solana transactions are gasless by default via Kora fee payer protocol. The user's wallet never needs SOL for gas. Requires publishableKey to be configured.

Create

const account = await openfort.accounts.solana.backend.create({
  wallet: 'pla_...', // Optional — associates the wallet with a player
})
// account.id      — 'acc_...'
// account.address — Base58 Solana address
// account.custody — 'Developer'

List & Get

const { accounts, total, nextPageToken } = await openfort.accounts.solana.backend.list({
  limit: 50,  // 1-100, default 10, optional
  skip: 0,    // optional
})

const account = await openfort.accounts.solana.backend.get({
  address: 'Base58Address...',
})
// Also: get({ id: 'acc_...' })
// Throws AccountNotFoundError if not found

Transfer SOL

const result = await account.transfer({
  to: 'FDx9mfVqTvXUaSPQDELwDtGgMqxirmAFsEK2s4YsKfsc',
  amount: 1_000_000n,       // In lamports (1 SOL = 1_000_000_000 lamports)
  cluster: 'devnet',         // 'devnet' | 'mainnet-beta'
  // token defaults to 'sol'
  computeUnitLimit: 200_000,   // Optional — auto-estimated via simulation if omitted
  computeUnitPrice: 50_000n,   // Optional — micro-lamports priority fee
})
console.log('Signature:', result.signature)

Transfer SPL Tokens (USDC, etc.)

// By token name
const usdcResult = await account.transfer({
  to: 'FDx9...',
  amount: 1_000_000n,  // In token base units (USDC: 6 decimals)
  token: 'usdc',
  cluster: 'devnet',
})

// By mint address
const splResult = await account.transfer({
  to: 'FDx9...',
  amount: 2_000_000n,
  token: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // Mint address
  cluster: 'devnet',
})

Send Transaction with Instructions

import { getTransferSolInstruction } from '@solana-program/system'
import { address, createNoopSigner } from '@solana/kit'

const account = await openfort.accounts.solana.backend.get({
  address: 'o24A5URLU3JNKg7AoeUrPsfsAo1NQeeAB4uQViAkpjq',
})

const ix = getTransferSolInstruction({
  source: createNoopSigner(address(account.address)),
  destination: address('FDx9mfVqTvXUaSPQDELwDtGgMqxirmAFsEK2s4YsKfsc'),
  amount: 10n,
})

const result = await openfort.accounts.solana.backend.sendTransaction({
  account,
  cluster: 'devnet',
  instructions: [ix],
  // computeUnitLimit — auto-estimated via simulation if omitted
  // computeUnitPrice — defaults to 50_000n micro-lamports
  // rpcUrl, wsUrl — custom endpoints (optional)
})
console.log('Signature:', result.signature)

Gasless flow under the hood:

  1. Kora provides fee payer address + blockhash
  2. Transaction built with Kora as fee payer
  3. Compute budget auto-estimated via simulateTransaction (falls back to 200k CU)
  4. User signs → Kora co-signs → RPC submits
  5. Confirmed via WebSocket subscription (60s timeout)

Send Raw Transaction (Pre-built Base64)

const result = await openfort.accounts.solana.backend.sendRawTransaction({
  account,
  cluster: 'devnet',
  transaction: base64EncodedTransaction,
})
// Internally decompiles, extracts instructions, re-wraps in gasless flow

Sign Data (Solana)

// Lower-level API
const signature = await openfort.accounts.solana.backend.sign(
  account.id,
  'hex_encoded_data',
)

// Account object methods
const sig1 = await account.signMessage({ message: 'Hello Solana' })
const sig2 = await account.signTransaction({ transaction: base64Tx })

Import / Export (Solana)

Same simplified flow as EVM — encryption is handled internally. Solana import accepts base58, hex with 0x, or raw hex. Auto-expands 32-byte seeds to 64-byte keypairs. Export returns base58 (standard Solana format).

// Import
const imported = await openfort.accounts.solana.backend.import({
  privateKey: '4YFq9y5f5hi77Bq8kDCE6VgqoAq...', // base58, hex with 0x, or raw hex
})

// Export
const privateKey = await openfort.accounts.solana.backend.export({
  id: account.id,
})
// privateKey is base58-encoded (standard Solana format)

Gas Sponsorship (Fee Sponsorship)

Gasless transactions require a two-step setup: create a policy (rules), then create a fee sponsorship (strategy) linked to that policy. This can be done via the SDK or the Openfort Dashboard.

Strategy Types

| Strategy | Description | |----------|-------------| | pay_for_user | Developer fully sponsors gas — user pays nothing | | charge_custom_tokens | User pays in ERC-20 tokens (fixed or dynamic exchange rate) | | fixed_rate | User pays a fixed token amount per transaction |

Create via SDK (Programmatic)

// Step 1: Create a policy with criteria rules
const policy = await openfort.policies.create({
  scope: 'project',          // 'project' (all accounts) or 'account' (single account)
  description: 'Sponsor gas on Base for USDC contract',
  rules: [{
    action: 'accept',
    operation: 'sponsorEvmTransaction',
    criteria: [
      { type: 'evmNetwork', operator: 'in', chainIds: [8453] },
      { type: 'evmAddress', operator: 'in', addresses: ['0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'] },
    ],
  }],
})

// Step 2: Create a fee sponsorship linked to that policy
const sponsorship = await openfort.feeSponsorship.create({
  name: 'Base USDC Gas Sponsorship',
  strategy: { sponsorSchema: 'pay_for_user' },
  policyId: policy.id,
})

// Step 3: Use sponsorship ID in transactions
await openfort.accounts.evm.backend.sendTransaction({
  account,
  chainId: 8453,
  interactions: [{ to: '0x...', value: '0', data: '0x...' }],
  policy: sponsorship.id,  // pol_... from sponsorship
})

Create via Dashboard

  1. Go to Openfort DashboardPolicies
  2. Create a policy with your desired criteria rules
  3. Go to Fee Sponsorship → Create and link to the policy
  4. Copy the fee sponsorship ID (pol_...)
  5. Use it in your code:
await openfort.accounts.evm.backend.sendTransaction({
  account,
  chainId: 84532,
  interactions: [{ to: '0x...', value: '0', data: '0x' }],
  policy: process.env.OPENFORT_FEE_SPONSORSHIP_ID!, // pol_... from dashboard
})

> Auto-discovery: When no explicit policy is passed to sendTransaction, project-scoped fee sponsorships are auto-discovered and the first matching one is applied.

Fee Sponsorship CRUD

// List
const sponsorships = await openfort.feeSponsorship.list()

// Get
const sponsorship = await openfort.feeSponsorship.get('pol_...')

// Update
await openfort.feeSponsorship.update('pol_...', { name: 'New Name' })

// Enable / Disable
await openfort.feeSponsorship.disable('pol_...')
await openfort.feeSponsorship.enable('pol_...')

// Delete (soft delete)
await openfort.feeSponsorship.delete('pol_...')

Charge Custom Tokens Strategy

const sponsorship = await openfort.feeSponsorship.create({
  name: 'Pay gas with USDC',
  strategy: {
    sponsorSchema: 'charge_custom_tokens',
    tokenContract: 'con_...',        // Contract ID from dashboard
    tokenContractAmount: '1000000',  // Amount in token base units
  },
  policyId: policy.id,
})

Policy Engine

Policies define rules that control which operations are allowed or rejected. They are evaluated server-side before any signing or transaction execution.

Key concepts:

  • Scope: 'project' (all accounts) or 'account' (single account)
  • Priority: Higher priority policies evaluated first
  • Fail-closed: No matching rule = operation rejected
  • Rules: Each policy has 1-10 ru

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.