Skip to main content

TypeScript SDK

info

SUAVE-viem is a fork of viem that will eventually be upstreamed but is currently still in a dynamic state.

Sending Confidential Compute Requests works slightly differently, but most other functionality is similar to interacting with any other EVM chain from viem.

This page describes how to work with the SUAVE-viem TypeScript SDK. The SDK simplifies interaction with the SUAVE Chain and provides easy-to-use functions to send transactions and query data. Below, you'll find steps on how to install the library and perform some basic actions.

Installation​

The @flashbots/suave-viem package is available on NPM, and can be installed with any NPM-based package manager, such as npm, yarn, or bun.

npm i @flashbots/suave-viem

Instantiation​

info

The steps described here assume you have SUAVE running locally.

First, you need to import necessary modules and instantiate the client. In your source code, you can copy and paste the following:

import {http} from '@flashbots/suave-viem';
import {getSuaveProvider} from '@flashbots/suave-viem/chains/utils';

// connect to your local SUAVE node
const SUAVE_RPC_URL = 'http://localhost:8545';
const suaveProvider = getSuaveProvider(http(SUAVE_RPC_URL));

Wallet Creation​

To interact with the SUAVE network, we'll need a wallet.

To make a wallet, we'll import the getSuaveWallet function. We'll also need to import the Hex type to make sure our hex strings are properly formatted.

We'll make a wallet using the pre-funded account. Update your code to match, or you can just copy-paste the whole snippet over what you already wrote.

// don't forget to include these new imports!
import {http, type Hex} from '@flashbots/suave-viem';
import {
getSuaveProvider,
getSuaveWallet,
type TransactionRequestSuave
} from '@flashbots/suave-viem/chains/utils';

// connect to your local SUAVE node
const SUAVE_RPC_URL = 'http://localhost:8545';
const suaveProvider = getSuaveProvider(http(SUAVE_RPC_URL));

// create a wallet with the pre-funded devenet account
const PRIVATE_KEY: Hex =
'0x91ab9a7e53c220e6210460b65a7a3bb2ca181412a8a7b43ff336b3df1737ce12';
const wallet = getSuaveWallet({
transport: http(SUAVE_RPC_URL),
privateKey: PRIVATE_KEY,
});

console.log('Wallet Address:', wallet.account.address);

You can now run this file:

bun run index.ts

And you should see the following printed to your terminal:

Wallet Address: 0xBE69d72ca5f88aCba033a063dF5DBe43a4148De0

Sending Requests​

SUAVE-viem can send Confidential Compute Requests (CCRs) and traditional ethereum transactions. Here we provide examples of both.

Transaction Types

The Confidential Compute Request transaction type is 0x43. This is the type you'll need to send data to your smart contracts confidentially.

If you're not sending confidential data, you can use the original ethereum tx types to send traditional transactions: 0x0 for legacy & 0x2 for EIP-1559.

Public Transaction​

Add this code to your existing index.ts file. We can check that ordinary, public Ethereum transactions are working by sending some ETH from the pre-funded devnet account to itself:

const gasPrice = await suaveProvider.getGasPrice();

const fundTx: TransactionRequestSuave = {
to: wallet.account.address,
value: 100000000000000001n,
gasPrice,
gas: 21000n,
type: '0x0',
};

const txHash = await wallet.sendTransaction(fundTx);
console.log('Sent tx', txHash);

Alternatively, if you need to sign the request without immediately sending it to Suave, you can instead use wallet.signTransaction and then suaveProvider.sendRawTransaction.

const signedTx = await wallet.signTransaction(fundTx);
const txHash = await suaveProvider.sendRawTransaction({
serializedTransaction: signedTx
});

Confidential Compute Request​

Let's create a new file called ccr.ts to explore how CCRs differ from the above, normal Ethereum transaction:

import {
http,
decodeEventLog,
encodeAbiParameters,
encodeFunctionData,
type Address,
type Hex
} from '@flashbots/suave-viem';
import {
getSuaveProvider,
getSuaveWallet,
type TransactionRequestSuave
} from '@flashbots/suave-viem/chains/utils';
import Contract from "./out/Contract.sol/Contract.json";

const SUAVE_RPC_URL = 'http://localhost:8545';
const suaveProvider = getSuaveProvider(http(SUAVE_RPC_URL));

// create a wallet with the pre-funded devenet account
const PRIVATE_KEY: Hex =
'0x91ab9a7e53c220e6210460b65a7a3bb2ca181412a8a7b43ff336b3df1737ce12';
const wallet = getSuaveWallet({
transport: http(SUAVE_RPC_URL),
privateKey: PRIVATE_KEY,
});

async function main() {

const gasPrice = await suaveProvider.getGasPrice();

const ccr: TransactionRequestSuave = {
to: <contract_address>,
value: 0n,
gasPrice,
gas: 690000n,
type: '0x43',
data: encodeFunctionData({
abi: Contract.abi,
functionName: exampleWithNumberInput,
}),
isEIP712,
confidentialInputs: encodeAbiParameters([
{type: 'uint256'}
], [
13n
]),
kettleAddress: "0xB5fEAfbDD752ad52Afb7e1bD2E40432A485bBB7F",
};

const ccrHash = await wallet.sendTransaction(ccr);
console.log("Your ccr tx hash is: ", ccrHash);
}

main();
  1. We specify transaction type 0x43 to indicate that this is a Confidential Compute Request.
  2. We call functions by ABI-encoding the function call in the data field, the same as you would for an Ethereum transaction.
  3. We specify isEIP712 (a boolean which defaults to true above), to signify that the request should be signed as EIP712 signed typed data. This is particularly useful as it allows users to interact with Suave without ever changing their RPC endpoint. We recommend you always set it to true.
  4. We set a new field called with confidential inputs.
  5. We provide our confidential data (also ABI-encoded) in the confidentialInputs field; this data is not revealed publicly, and is only known to the kettle.
  6. The kettleAddress we use is specific to the local devnet. On a public testnet, this value is different. If you're looking for that address you can find it here.

Watching Pending Transactions​

It's likely the case that you don't just want to console.log your transaction hash, but want to do something after you have received it and are sure the transaction has been successful.

If you're just sending Ether - or some other simple operation as described in the Public Transaction section, then you can add something like this:

// Watch for pending transactions
suaveProvider.watchPendingTransactions({
async onTransactions(transactions) {
for (const hash of transactions) {
try {
const receipt = await suaveProvider.getTransactionReceipt({hash});
console.log('Transaction Receipt:', receipt);
} catch (error) {
console.error('Error fetching receipt:', error);
}
}
},
});

Run index.ts again and you should see something like this:

$ bun run index.ts
Wallet Address: 0xBE69d72ca5f88aCba033a063dF5DBe43a4148De0
Sent tx: 0xcf95ee9d30d4c865444f60d03727b0c04f8738501b394ebc078e56eeb733a80a
Transaction Receipt: {
blockHash: "0x61c7ef6d56bf16d75a49dc06b82d82df74c1f8f02c95c0a10abe369b03a580a2",
blockNumber: 1n,
contractAddress: null,
cumulativeGasUsed: 21000n,
effectiveGasPrice: 1000000001n,
from: "0xbe69d72ca5f88acba033a063df5dbe43a4148de0",
gasUsed: 21000n,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "success",
to: "0xbe69d72ca5f88acba033a063df5dbe43a4148de0",
transactionHash: "0xcf95ee9d30d4c865444f60d03727b0c04f8738501b394ebc078e56eeb733a80a",
transactionIndex: 0,
type: "legacy",
}

If you're interacting with a contract and need specific logs, as is the case in the Confidential Compute Request section, then you can consider extending it to look like this:

suaveProvider.watchPendingTransactions({
async onTransactions(transactions) {
for (const hash of transactions) {
try {
const receipt = await suaveProvider.getTransactionReceipt({hash});
console.log('Transaction Receipt:', receipt);
if (receipt.status === 'success' && receipt.logs.length > 0) {
const decodedLogs = decodeEventLog({
abi: Contract.abi,
...receipt.logs[0],
})
console.log("decoded logs", decodedLogs)
}
} catch (error) {
console.error('Error fetching receipt:', error);
}
}
},
});

Run ccr.ts again and you should see something like this:

$ bun run ccr.ts
Transaction Receipt: {
blockHash: "0xee533da6fad9ae95b7b3f5dd74711d011b0c984490274b3936159c5555e72e32",
blockNumber: 7n,
contractAddress: null,
cumulativeGasUsed: 120532n,
effectiveGasPrice: 4200000000n,
from: "0xbe69d72ca5f88acba033a063df5dbe43a4148de0",
gasUsed: 120532n,
logs: [
{
address: "0xd594760b2a36467ec7f0267382564772d7b0b73c",
topics: [ "0x9ec8254969d1974eac8c74afb0c03595b4ffe0a1d7ad8a7f82ed31b9c8542591"
],
data: "0x000000000000000000000000000000000000000000000000000000000000000d",
blockNumber: 7n,
transactionHash: "0x98619cc542c1aa6d427e4ce6edbb4eeef5d98c0dcf96f094fe93be28632b582b",
transactionIndex: 0,
blockHash: "0xee533da6fad9ae95b7b3f5dd74711d011b0c984490274b3936159c5555e72e32",
logIndex: 0,
removed: false,
}
],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000004000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "success",
to: "0xd594760b2a36467ec7f0267382564772d7b0b73c",
transactionHash: "0x98619cc542c1aa6d427e4ce6edbb4eeef5d98c0dcf96f094fe93be28632b582b",
transactionIndex: 0,
type: "0x50",
}
decoded logs {
eventName: "NumberSet",
args: {
number: 13n,
},
}

For viem-related questions, the viem docs are a great resource. suave-viem is just a fork of viem, so you can expect it to work pretty much the same as vanilla viem.