4.1. Quick Integration: Native
Overview of Core Functions
The SurferMonkey SDK simplifies the process of executing privacy-compliant blockchain transactions by providing two core functions: createDeposit()
and createWithdraw()
, along with an additional function for enhanced privacy, getInstitutionMembershipProof()
. These functions abstract the complexity of interacting with the blockchain and ensure that the entire process remains secure and compliant.
-
getInstitutionMembershipProof()
: This function is used to generate the User Institution Membership Proof (userMerkleProof
), which is a cryptographic proof that the user is part of the institution's whitelist. The institution backend manages this step to ensure user privacy while providing the necessary proof for subsequent transactions. -
createDeposit()
: This function is used to generate a deposit object, which includes data that must be used in subsequent stages to process the transaction. An automated nested step here is to create a ZKP Leaf Verification which is required to lock the asset into the Universal Plugin. This is essentially the first step in preparing a secure transaction. -
createWithdraw()
: This function is used to generate a Withdraw Zero-Knowledge Proof (ZKP) that ensures the privacy of the transaction details. Once the deposit is created, this function is used to prove the legitimacy of the action to withdraw the funds from the Universal Plugin in a privacy-preserving manner.
Treat the createDeposit()
response with the same level of security as your private keys:
- Do not share any information from the response.
- Do not modify any parameters. The SDK and the ZKP Leaf Verification are designed to ensure security, consistency, and error handling. Altering any value from the
createDeposit()
response could compromise the transaction, potentially resulting in the loss of your funds.
Blockchain Flow Overview (Native)
The diagram Figure 5 illustrates the flow of a native asset transaction involving both deposit and withdrawal phases.
- Deposit phase, Alice deposits her native asset into the Universal Plugin..
- Withdraw phase, Alice sends a Zero-Knowledge Proof (ZKP) to the Relayer, the Relayer uses this ZKP to initiate the withdrawal through the Mixer, which in turn unlocks the funds from the Universal Plugin. Finally, the Universal Plugin transfers the native funds to the Proxy, which completes the transaction by sending the funds to Bob via an agnostic transfer call.
Figure 5. - Native asset transfer blockchain flow
Step-by-Step Minimal Integration Example Native
Below is a step-by-step guide to help you integrate SurferMonkey SDK using the core functions. In this example, we illustrate a minimal setup for a native asset transaction.
-
Import the SDK
First, import the SurferMonkey SDK in your JavaScript file to start using its functionality.
const SurferMonkey = require('@surfermonkey/sdk_babyulu'); // SurferMonkey SDK
const ethers = require('ethers'); -
Define Input Transaction Parameters
Create the input transaction object to specify the amount and asset details.
infoFor Native Asset the field
ASSET_ID
is address(0) =0x0000000000000000000000000000000000000000000000000000000000000000
const inputTx = {
AMOUNT_TOTAL: "10000000000000", // Amount to be locked (in uint256 format)
ASSET_ID: "0x0000000000000000000000000000000000000000" // Native asset ID is address(0)
}; -
Define Output Transaction Parameters
Define how the locked assets will be distributed. This output specifies one recipient and one smart contract call.
infoFor Native Transfers, specify the
targetSC
as the SurferMonkey Proxy Smart Contract address. And use the following function details:- Function Header:
"function transferEth(address targetAddress, uint256 amount)"
- Function Name:
"transferEth"
const outputTxArr = [
{
amountOutput: "10000000000000",
smartContractCalls: [
{
payloadAmountNative: "10000000000000",
targetSC: "0xSurferMonkeyProxy", // For Native asset transfers
payloadObject: { // Proxy transfer function header
functionHeader: "function transferEth(address targetAddress, uint256 amount)",
functionName: "transferEth",
payloadParmsArr: ["0xRecipientAddress", "10000000000000"]
}
}
]
}
]; - Function Header:
-
Create the User Message
Prepare the user message configuration that will be passed to the
createDeposit()
function.warningThe
userEOA
must be included in the whitelist Merkle Tree by the institution admin. Failure to do so will prevent the Deposit transaction from being processed successfully.// Get User Institution Merkle Proof from the backend
const userMerkleProof = await SurferMonkey.getInstitutionMembershipProof({
BACKEND_RPC,
userEOA: "0xYourEvmAddressHere",
institutionLeaves: ["0xValidUserAddress1", "0xValidUserAddress2", "0xYourEvmAddressHere"]
})
// Provide the userMerkleProof to the client device
const userMessage = {
userEOA: "0xYourEvmAddressHere",
inputTx: inputTx,
outputTxArr: outputTxArr,
numberTxOut: 1,
pubKey: ["PubKeyString[0]", "PubKeyString[1]"],
chainID: "ChainIdentifier",
userMerkleProof: userMerkleProof
}; -
Create Deposit object, Leaf Verification ZKP and Lock fonds into the Universal Plugin
Call the
createDeposit()
function to obtain the data structures and ZKP required to lock the funds into the Universal Plugin. Then, proceed with sending the ZKP and the msg.value (only positive for native) into the Universal Plugindeposit()
function to lock the funds.warningTransfer msg.value in the Deposit Call only when locking Native Assets. For ERC20 deposits, msg.value must be set to 0; otherwise, the transaction will revert.
const BACKEND_RPC = "https://your-surfermonkey-backend-rpc-url.com";
const UP_ABI = require('UniversalPlugin.json');
const deposit = await SurferMonkey.createDeposit(
{
BACKEND_RPC,
userMessage,
verbose: true
});
// Proceed with submitting the deposit to the blockchain
const USER_SIGNER = new ethers.Wallet(PRIV_KEY, PROVIDER);
const UniversalPlugin_SC = new ethers.Contract("0xUniversalPluginAddress", UP_ABI, USER_SIGNER);
const submitDeposit = await UniversalPlugin_SC.Deposit(
deposit.SOLIDITY_DATA.a,
deposit.SOLIDITY_DATA.b,
deposit.SOLIDITY_DATA.c,
deposit.SOLIDITY_DATA.Input,
{
value: deposit.amountTotal, // For Native asset.
maxPriorityFeePerGas: 25000000000,
maxFeePerGas: 27000000000,
gasLimit: 5142880
}
); -
Generate Withdraw Zero-Knowledge Proof
After creating and submitting the deposit, generate the Zero-Knowledge Proof (ZKP) by calling
createWithdraw()
.infoThe
targetLeafChild
parameter specifies the index of the output transaction withinoutputTxArr
that you intend to withdraw from the Universal Plugin.const getZKPSignalsObject = {
BACKEND_RPC:,
depositJSON: deposit,
targetLeafChild: 0, // Use the first output transaction in this example
verbose: true
};
const zkpSolidityData = await SurferMonkey.createWithdraw(getZKPSignalsObject);
console.log("ZKP generated:", zkpSolidityData); -
Submit Zero-Knowledge Proof to Blockchain
Finally, use ethers.js or any blockchain library to submit the generated ZKP.
warningUse a different Relayer address for Withdraw than the one used for Deposit to maintain privacy. Alternatively, use the SurferMonkey Relayer for enhanced anonymity.
const TARGET_MIXER = new ethers.Contract("0xMixerAddress", MIXER_ABI, RELAYER_SIGNER);
const tx = await TARGET_MIXER.withdraw(
zkpSolidityData.a,
zkpSolidityData.b,
zkpSolidityData.c,
zkpSolidityData.Input,
deposit.depositArr[0].payloadData,
deposit.depositArr[0].targetSC,
deposit.depositArr[0].payloadAmountPerCall,
{
value: 0,
maxPriorityFeePerGas: Number(25000000000),
maxFeePerGas: Number(27000000000),
gasLimit: Number(5142880)
}
);
console.log("ZKP transaction submitted:", tx.hash);
🎉 Congrats on Rocking Through the Minimal Demo! 🎉
You're now all set to take privacy-compliant blockchain transactions to the next level with SurferMonkey. 🚀✨
Full integration example code
// Full Script: Minimal Integration Example for Native asset
// Import the SDK
const SurferMonkey = require('@surfermonkey/sdk_babyulu'); // SurferMonkey SDK
const ethers = require('ethers');
const UP_ABI = require('UniversalPlugin.json');
const MIXER_ABI = require('SurferMonkeyMixer.json');
const ALICE ="0xYourEvmAddressHere";
// Define Input Transaction Parameters
const inputTx = {
AMOUNT_TOTAL: "10000000000000", // Amount to be locked (in uint256 format)
ASSET_ID: "0x0000000000000000000000000000000000000000" // Native asset ID is address(0)
};
// Define Output Transaction Parameters
const outputTxArr = [
{
amountOutput: "10000000000000",
smartContractCalls: [
{
payloadAmountNative: "10000000000000",
targetSC: "0xSurferMonkeyProxy", // For Native asset transfers
payloadObject: { // For Native asset transfers use the Proxy transfer function header
functionHeader: "function transferEth(address targetAddress, uint256 amount)",
functionName: "transferEth",
payloadParmsArr: ["0xRecipientAddress", "10000000000000"]
}
}
]
}
];
// Create Deposit and Lock Funds into the Universal Plugin
async function createDepositAndLockFunds() {
try {
// Get User Institution Merkle Proof from the backend
const userMerkleProof = await SurferMonkey.getInstitutionMembershipProof({
BACKEND_RPC,
userEOA: ALICE,
institutionLeaves: ["0xValidUserAddress1", "0xValidUserAddress2", ALICE]
})
// Create the User Message
const userMessage = {
userEOA: ALICE,
inputTx: inputTx,
outputTxArr: outputTxArr,
numberTxOut: 1,
pubKey: ["PubKeyString[0]", "PubKeyString[1]"],
chainID: "ChainIdentifier",
userMerkleProof: userMerkleProof
};
const BACKEND_RPC = "https://your-surfermonkey-backend-rpc-url.com";
const PRIV_KEY = "0xYourPrivateKeyHere";
const PROVIDER = new ethers.providers.JsonRpcProvider("https://your-blockchain-rpc-url.com");
const USER_SIGNER = new ethers.Wallet(PRIV_KEY, PROVIDER);
const deposit = await SurferMonkey.createDeposit({ BACKEND_RPC, userMessage, verbose: true });
// Proceed with submitting the deposit to the blockchain
const UniversalPlugin_SC = new ethers.Contract("0xUniversalPluginAddress", UP_ABI, USER_SIGNER);
const submitDeposit = await UniversalPlugin_SC.Deposit(
deposit.SOLIDITY_DATA.a,
deposit.SOLIDITY_DATA.b,
deposit.SOLIDITY_DATA.c,
deposit.SOLIDITY_DATA.Input,
{
value: deposit.amountTotal, // For Native asset. Remember: In ERC20, msg.value shall be Zero
maxPriorityFeePerGas: Number(25000000000),
maxFeePerGas: Number(27000000000),
gasLimit: Number(5142880)
}
);
console.log("Deposit transaction submitted:", submitDeposit.hash);
await submitDeposit.wait(); // Wait for transaction to get minted
// wait 10s for blockchain probabilistic finality
await new Promise(resolve => setTimeout(resolve, 10000));
// Generate Zero-Knowledge Proof
const getZKPSignalsObject = {
BACKEND_RPC,
depositJSON: deposit,
targetLeafChild: 0, // Use the first output transaction in this example
verbose: true
};
const zkpSolidityData = await SurferMonkey.createWithdraw(getZKPSignalsObject);
console.log("ZKP generated:", zkpSolidityData);
// Submit Zero-Knowledge Proof to Blockchain
const RELAYER_SIGNER = new ethers.Wallet("0xRelayerPrivateKeyHere", PROVIDER);
const TARGET_MIXER = new ethers.Contract("0xMixerAddress", MIXER_ABI, RELAYER_SIGNER);
const tx = await TARGET_MIXER.withdraw(
zkpSolidityData.a,
zkpSolidityData.b,
zkpSolidityData.c,
zkpSolidityData.Input,
deposit.depositArr[0].payloadData,
deposit.depositArr[0].targ etSC,
deposit.depositArr[0].payloadAmountPerCall,
{
value: 0,
maxPriorityFeePerGas: Number(25000000000),
maxFeePerGas: Number(27000000000),
gasLimit: Number(5142880)
}
);
console.log("ZKP transaction submitted:", tx.hash);
} catch (error) {
console.error("Error during integration:", error);
}
}
// Execute the function
createDepositAndLockFunds();