Private Transactions
As discussed previously, we need to craft a ZTransaction
struct that represents a shielded transaction. This is so that we can invoke the transact
method on-chain and execute the private transaction.
We also saw previously how to initialize an instance (variable zkfi
) of SDK.
Creating Transactions
The SDK exposes easy API for developers to specify what transaction they want to send. We will create a transaction using TransactionRequest
and TransactionOptions
object and pass it to createTransaction(...)
method of our SDK instance zkfi
.
Since, initially we have no funds in our shielded account, we'd like to deposit some assets into it. Let's say, we'd like to deposit 2 WETH
. To achieve this, we create a request with the following parameters:
So what did we do? Let's break it down:
TransactionRequest
We set
type
toTransactionType.DEPOSIT
since we want to do a deposit from our public wallet to shielded account.We set
assetIds
to include only one asset of id65537
(0x010001
in hex) which is the asset id forWETH
in Labyrinth. We only want to deposit one asset i.eWETH
. You can get a list of all supported assets fromcontractService::getAssets()
.Let
values
to include only one value which is2 ether
. Since it is at the same index asWETH
inassetIds
, it is the value ofWETH
we want to deposit.We set
feeAssetId
to0
.0
is a dummy/placeholder asset id and does not represent any token or asset in reality. We set it to0
because in aDEPOSIT
transaction, we are not going to pay any fee, from any of the assets mentioned inassetIds
. This is because we will be paying gas fees ourselves from our wallets.We set
to
to the address, shielded address, or ENS of the target. In this case, it is the recipient of the deposit i.e.bob.eth
. We assume that the address associated withbob.eth
is already registered with Labyrinth protocol so that it can be ultimately resolved to a shielded address where the funds will be sent.
TransactionOptions
: -revokerId
: The id of the revoker chosen to keep this transaction compliant.
Now we create the Transaction
:
Now to authorize this transaction if it needs to be signed. For this, we pass the created transaction instance tx
to zkfi.signTransaction(...)
the method.
We've created the transaction finally! But wait. it is not yet ready to be sent on-chain! Remember we need a shielded transaction ZTransaction
for this. To create a shielded transaction we need to hide sensitive values and prove our signed transaction.
Again, zkfi
instance provides a simple proveTransaction(...)
method to generate a ZK-SNARK proof proving the validity of the transaction tx
and yield an instance of the ZTransaction
class.
Finally, we have a private and fully compliant transaction represented by the ZTransaction
instance - ztx
. If you want to send this transaction on-chain, you can simply convert it to proper input to the contract's transact
by calling ztx.toSolidityInput()
.
Congrats!
Creating a private asset transfer
Now that we have some funds in our shielded account, let's use it! Let's transfer 1 WETH
privately to the alice.eth
(again, assuming that the public address associated with alice.eth
is already registered).
First, we create a TRANSFER
request.
We specify the same kind of fields in our request, except this time we specify a feeAssetId
of WETH
. We specify that we want to pay transaction fees with the WETH
asset from our shielded account.
TransactionOptions
:
- revokerId
: The id of the revoker chosen.
- We set the paymaster
to the address of the paymaster contract (ERC-4337), which will sponsor this transaction and will be paid in return for it.
- We finally set theviaBundler
option to true, as we want this transaction to be relayed through the bundler thus keeping our privacy intact.
But why and where are we paying fees?
Unlike the DEPOSIT
, we don't want to send this transaction with our public wallet. This will expose our wallet's public address on block explorers and privacy will be harmed because anyone can inspect and match the previous deposit transaction and this transfer transaction. Hence, linking them. To resolve this issue, we will send the transaction through a third party, in exchange for some fee. One possible third party is the bundler node from EIP 4337. This fee, paid in WETH
, is sent to the bundler ultimately. Note that we don't input the fee value, it is determined using SDK automatically as of now - you can see the value after transaction creation using the tx.feeValue
property.
The rest of the steps remain the same - creating, signing and proving the transaction to get the ZTransaction
.
Now, you can make calling transact(...)
method with ztx.toSolidityInput()
an EIP-4337 UserOperation and relay it via bundler.
We are currently working on a module to convert ZTransaction
to a EIP-4337 UserOperation
and send the transaction via bundler with a single call.
Withdraw from a shielded account
In case for any reason you want to withdraw assets from your shielded account to any public address you can follow the same steps as the above transfer transaction, but with type
set to TransactionType.WITHDRAW
.
In this case what you specify as to
is a public address or ENS (it is not resolved to a shielded address) where funds will be sent.
Last updated