Kaia Safe API Kit
API-Kit is your go-to kit for securely interacting with the Safe Transaction API. The core of this kit is to allow valid signers to propose and share transactions with the other signers of a Safe, send the signatures to the service to collect them, and get information about a Safe (like reading the transaction history, pending transactions, enabled Modules and Guards, etc.), among other features.
Quickstart β
By the end of this guide, you will be able to propose transactions to the service and obtain the owners' signatures for execution.
Prerequisites β
- Node.js and npm
- A Safe with several signers
Set up environment β
Step 1: Create a project directory.β
Copy and paste this command in your terminal to create the project folder.
mkdir kaiasafe-api-kitcd kaiasafe-api-kit
Step 2: Initialize an npm project.β
Copy and paste this command in your terminal to create a package.json
file.
npm init -y
Step 3: Install dependencies.β
Using API-Kit is as simple as running the installation command below:
- npm
- yarn
npm install @safe-global/api-kit @safe-global/protocol-3 @safe-global/safe-core-sdk-types
yarn add @safe-global/api-kit @safe-global/protocol-kit @safe-global/safe-core-sdk-types
Step 4: Import dependencies.β
Create a file named app.js
. This is where all our code snippets for this interaction would live.
Copy and paste these necessary imports at the top of the app.js
file.
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β
To efficiently illustrate how API-Kit works, we will use a Safe account setup with two or more signers, and threshold two, so we have multiple signatures that need to be collected when executing a transaction.
Copy and paste the following under the import statements in your app.js
file:
// 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β
To initialize API Kit, we need to create an instance of the API Kit.
In chains where the Safe Transaction Service is supported, it's enough to specify the chainId property.
const apiKit = new SafeApiKit.default({ chainId: 1001n, txServiceUrl: 'https://docs-safe.kaia.io/txs-baobab/api'})
As you can see above, we included custom service using the optional txServiceUrl property.
Step 2: Initialize Protocol Kitβ
To handle transactions and signatures, we need to create an instance of the Protocol Kit (a kit that enables developers to interact with Safe Smart Accounts using a TypeScript interface) with the provider, signer and safeAddress.
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β
One of the core features of API Kit is to enable valid signers to share transactions with other signers. But before this is done, any of the Safe signers needs to initiate the process by creating a proposal of a transaction. This transaction is then sent to the service to make it accessible by the other owners so they can give their approval and sign the transaction as well.
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β
API Kit provides us different methods to retrieve pending transactions depending on the situation. For this guide, we will retrieve a transaction given the Safe transaction hash and other available methods commented out below:
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β
The next thing to do is to sign the transaction with the Protocol Kit and submit the signature to the Safe Transaction Service using the confirmTransaction method.
const protocolKitOwner2 = await Safe.default.init({ provider: RPC_URL, signer: OWNER_2_PRIVATE_KEY, safeAddress: SAFE_ADDRESS})const signature2 = await protocolKitOwner2.signHash(safeTxHash)// Confirm the Safe transactionconst signatureResponse = await apiKit.confirmTransaction( safeTxHash, signature2.data)
Step 6: Execute the transactionβ
The Safe transaction is now ready to be executed. This can be done using the Safe Wallet Web interface, the Protocol Kit, the Safe CLI or any other tool that's available.
For this last step, we executed the safe transaction using Protocol Kit.
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/${hash}`)
At the end, the full code in app.js
should look like this:
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, 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)// 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()// 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)// Confirm the Safe transactionconst 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/${hash}`)
Visit the API Kit Reference for more information, and navigate to Github to access the full source code for this guide.