Safe API Kit
safe.kaia.io will sunset on August 9, 2026. Please use Safe Wallet for Kaia Network at app.safe.global to manage your accounts going forward. Your existing Safe Accounts will be automatically compatible with Safe Wallet.
API Kit helps you interact securely with the Safe Transaction Service. Valid signers can propose and share transactions, collect signatures off-chain, and read Safe information (history, pending transactions, modules, guards, and more).
Kaia chain IDs: 8217 (Mainnet), 1001 (Kairos). As safe.kaia.io sunsets, prefer Safe Global Transaction Service configuration for supported chains. If you still need a custom txServiceUrl, confirm the endpoint you use remains available after the migration.
Quickstart
By the end of this guide, you will propose a transaction to the service and collect owner signatures for execution.
Prerequisites
- Node.js and npm
- A Safe with several signers on Kairos (or Mainnet)
Set up environment
Step 1: Create a project directory
mkdir kaiasafe-api-kitcd kaiasafe-api-kit
Step 2: Initialize an npm project
npm init -y
Step 3: Install dependencies
- npm
- yarn
npm install @safe-global/api-kit@2.4.2 @safe-global/protocol-kit@4.0.2 @safe-global/safe-core-sdk-types@5.0.2
yarn add @safe-global/api-kit@2.4.2 @safe-global/protocol-kit@4.0.2 @safe-global/safe-core-sdk-types@5.0.2
Step 4: Import dependencies
Create app.js and add:
import SafeApiKit from '@safe-global/api-kit'import Safe from '@safe-global/protocol-kit'import { OperationType} from '@safe-global/safe-core-sdk-types'
Step 5: Configure setup
Use a Safe with at least two owners and threshold two so multiple signatures are required.
// https://chainlist.org/?search=kaia&testnets=trueconst RPC_URL = 'https://public-en-kairos.node.kaia.io'const SAFE_ADDRESS = "<REPLACE WITH SAFE PUBLIC ADDRESS HERE>"; // 2 Owner Safe Address Ex: 0x123.... SAFE SHOULD const OWNER_1_ADDRESS = "<REPLACE WITH OWNER 1 PUBLIC KEY HERE>"; // ONLY OWNER 1 and SAFE ADDRESS Need to have some test KAIA balanceconst OWNER_1_PRIVATE_KEY = "<REPLACE WITH OWNER 1 PRIVATE KEY HERE>";const OWNER_2_PRIVATE_KEY = "<REPLACE WITH OWNER 2 PRIVATE KEY HERE>"; // OWNER 2 need not have any test KAIAconst TO_ADDRESS = OWNER_1_ADDRESS; // Receiver address of sample transaction who receives 1 wei
Use API Kit
Step 1: Initialize API Kit
On chains where the Safe Transaction Service is supported, specifying chainId is often enough. You may still pass a custom txServiceUrl when using a dedicated endpoint (verify it remains valid after the Kaia UI sunset).
const apiKit = new SafeApiKit.default({ chainId: 1001n, // Kairos; use 8217n for Kaia Mainnet // Custom URL if needed — confirm it remains valid after safe.kaia.io sunset // Prefer Safe Global Transaction Service config when Kaia is listed: // https://docs.safe.global/core-api/transaction-service-overview txServiceUrl: 'https://docs-safe.kaia.io/txs-baobab/api'})
Step 2: Initialize Protocol Kit
const protocolKitOwner1 = await Safe.default.init({ provider: RPC_URL, signer: OWNER_1_PRIVATE_KEY, safeAddress: SAFE_ADDRESS})
Step 3: Propose a transaction to the service
const safeTransactionData = { to: TO_ADDRESS, value: '1', // 1 wei data: '0x', operation: OperationType.Call}const safeTransaction = await protocolKitOwner1.createTransaction({ transactions: [safeTransactionData]})const safeTxHash = await protocolKitOwner1.getTransactionHash(safeTransaction)const signature = await protocolKitOwner1.signHash(safeTxHash)// 2. Propose transaction to the servicetry { await apiKit.proposeTransaction({ safeAddress: SAFE_ADDRESS, safeTransactionData: safeTransaction.data, safeTxHash, senderAddress: OWNER_1_ADDRESS, senderSignature: signature.data })} catch(err) { console.log(err)}
Step 4: Retrieve pending transaction
const transaction = await apiKit.getTransaction(safeTxHash)// const transactions = await service.getPendingTransactions()// const transactions = await service.getIncomingTransactions()// const transactions = await service.getMultisigTransactions()// const transactions = await service.getModuleTransactions()// const transactions = await service.getAllTransactions()
Step 5: Confirm the transaction
Sign with Protocol Kit and submit the signature via confirmTransaction.
const protocolKitOwner2 = await Safe.default.init({ provider: RPC_URL, signer: OWNER_2_PRIVATE_KEY, safeAddress: SAFE_ADDRESS})const signature2 = await protocolKitOwner2.signHash(safeTxHash)const signatureResponse = await apiKit.confirmTransaction( safeTxHash, signature2.data)
Step 6: Execute the transaction
Execute via Safe Wallet, the Protocol Kit, the Safe CLI, or another compatible tool.
const safeTxn = await apiKit.getTransaction(safeTxHash);const executeTxReponse = await protocolKitOwner1.executeTransaction(safeTxn)const receipt = await executeTxReponse.transactionResponse?.wait();console.log('Transaction executed:');console.log(`https://kairos.kaiascan.io/tx/${receipt.hash}`)
Full app.js example:
import SafeApiKit from '@safe-global/api-kit'import Safe from '@safe-global/protocol-kit'import { OperationType} from '@safe-global/safe-core-sdk-types'// https://chainlist.org/?search=kaia&testnets=trueconst RPC_URL = 'https://public-en-kairos.node.kaia.io'const SAFE_ADDRESS = "<REPLACE WITH SAFE PUBLIC ADDRESS HERE>"; // 2 Owner Safe Address Ex: 0x123.... SAFE SHOULD const OWNER_1_ADDRESS = "<REPLACE WITH OWNER 1 PUBLIC KEY HERE>"; // ONLY OWNER 1 and SAFE ADDRESS Need to have some test KAIA balanceconst OWNER_1_PRIVATE_KEY = "<REPLACE WITH OWNER 1 PRIVATE KEY HERE>";const OWNER_2_PRIVATE_KEY = "<REPLACE WITH OWNER 2 PRIVATE KEY HERE>"; // OWNER 2 need not have any test KAIAconst TO_ADDRESS = OWNER_1_ADDRESS; // Receiver address of sample transaction who receives 1 weiconst apiKit = new SafeApiKit.default({ chainId: 1001n, // Confirm endpoint after safe.kaia.io sunset; see Safe Global TX Service docs txServiceUrl: 'https://docs-safe.kaia.io/txs-baobab/api'})const protocolKitOwner1 = await Safe.default.init({ provider: RPC_URL, signer: OWNER_1_PRIVATE_KEY, safeAddress: SAFE_ADDRESS})// 1. Create transactionconst safeTransactionData = { to: TO_ADDRESS, value: '1', // 1 wei data: '0x', operation: OperationType.Call}const safeTransaction = await protocolKitOwner1.createTransaction({ transactions: [safeTransactionData]})const safeTxHash = await protocolKitOwner1.getTransactionHash(safeTransaction)const signature = await protocolKitOwner1.signHash(safeTxHash)// 2. Propose transaction to the servicetry { await apiKit.proposeTransaction({ safeAddress: SAFE_ADDRESS, safeTransactionData: safeTransaction.data, safeTxHash, senderAddress: OWNER_1_ADDRESS, senderSignature: signature.data })} catch(err) { console.log(err)}console.log("Transaction hash is "+safeTxHash)const transaction = await apiKit.getTransaction(safeTxHash)// 3. Confirmation from Owner 2const protocolKitOwner2 = await Safe.default.init({ provider: RPC_URL, signer: OWNER_2_PRIVATE_KEY, safeAddress: SAFE_ADDRESS})const signature2 = await protocolKitOwner2.signHash(safeTxHash)const signatureResponse = await apiKit.confirmTransaction( safeTxHash, signature2.data)console.log(signatureResponse)// 4. Execute transactionconst safeTxn = await apiKit.getTransaction(safeTxHash);const executeTxReponse = await protocolKitOwner1.executeTransaction(safeTxn)const receipt = await executeTxReponse.transactionResponse?.wait();console.log('Transaction executed:');console.log(`https://kairos.kaiascan.io/tx/${receipt.hash}`)
See the API Kit Reference and example snippets for more detail.