Skip to main content

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.

danger

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.

  1. 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');
  2. Define Input Transaction Parameters

    Create the input transaction object to specify the amount and asset details.

    info

    For 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)
    };
  3. Define Output Transaction Parameters

    Define how the locked assets will be distributed. This output specifies one recipient and one smart contract call.

    info

    For 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"]
    }
    }
    ]
    }
    ];
  4. Create the User Message

    Prepare the user message configuration that will be passed to the createDeposit() function.

    warning

    The 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
    };
  5. 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 Plugin deposit() function to lock the funds.

    warning

    Transfer 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
    }
    );
  6. Generate Withdraw Zero-Knowledge Proof

    After creating and submitting the deposit, generate the Zero-Knowledge Proof (ZKP) by calling createWithdraw().

    info

    The targetLeafChild parameter specifies the index of the output transaction within outputTxArr 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);
  7. Submit Zero-Knowledge Proof to Blockchain

    Finally, use ethers.js or any blockchain library to submit the generated ZKP.

    warning

    Use 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();