Integrate Directly in Smart Contracts

This is a somewhat antiquated page. Please reach out to us.

It is recommended developers familiarize themselves with how the Peanut Protocol works prior to attempting direct contract interactions.

The Peanut Smart Contracts are designed for sending non-front-runnable link payments, which can be in the form of various assets. Including ETH, ERC-20 tokens, ERC-721 tokens, ERC-1155 tokens, or combinations thereof!

Contracts use asymmetric ECDSA encryption to verify claim permissions and prevent front running.

To create a Link there are a few steps required.

  1. Generate the seed and derive asymmetric key pair from ID.

  2. Call the smart contract to deposit funds.

  3. Get index from the event log emitted by deposit call.

Step 1: Generating Keys

Each Peanut Link is protected by a key. The key should be securely created for each Link using secure random data generation features of the platform used.

Whilst anything can be used as a key, the following should be avoided:

  • Short seeds less than 16 characters

  • Words or common patterns (e.g. aaaaaa or 123456)

The SDK uses Ethers to generate the ECDSA key from a seed. Any equivalent function in different languages should also be able to do the same.

function generateKeysFromString(string) {
    var privateKey = ethers.utils.keccak256(
			ethers.utils.toUtf8Bytes(string))
    var wallet = new ethers.Wallet(privateKey)
    return {
        address: wallet.address,
        privateKey: privateKey,
    }
}

It's worth checking that the output from any other crypto library matches the output of the generateKeysFromString function within util.js.

The address and private key from this generation should be stored for later use in the next steps.

Step 2: Calling the Smart Contract

The makeDeposit function allows users to create a Link from a deposit. It handles various token types (ETH, ERC-20, ERC-721, ERC-1155, etc) and stores deposit information in the deposits array.

This function should be called with the correct parameters for the type of crypto being sent along with the last 20 bytes of the public key generated in the previous step.

Finding the latest address of the contract on the chain in question can be done by looking at the registry file in the github repo. Please note this same smart contract will need to be called in order to claim the funds.

The following table lists all parameter types for the makeDeposit function.

ParamDescription

_tokenAddress

Address of the token being stored in the Peanut link

_contractType

Enumeration of type of crypto being wrapped in a link.

0 = ETH / native chain token

1 = ERC20 token

2 = ERC721 token

3 = ERC1155 token

4 = ECO token

_amount

Amount of token being sent

_tokenId

ID for ERC721 and ERC1155 type contracts (leave as 0 for any type apart from 2 or 3)

_pubKey20

Last 20 bytes of the public key for ECDSA signing

function makeDeposit(
    address _tokenAddress,
    uint8 _contractType,
    uint256 _amount,
    uint256 _tokenId,
    address _pubKey20
) public payable nonReentrant returns (uint256) {
    // Function for making a deposit.
    // ...
}

Once the transaction calls makeDeposit successfully, it will emit a log confirming the deposit and index slot where the funds are held.

event DepositEvent(
    uint256 indexed _index,
    uint8 indexed _contractType,
    uint256 _amount,
    address indexed _senderAddress
);

For example, in this deposit the event log 104 contains information about the funds stored and what storage index they occupy.

From the event log, we can see the deposit was made to index 0 for an ERC20 token.

All the information supplied in the event log will help us construct the Peanut Link. Remember the expected format:

https://peanut.to/claim?c=CHAIN&i=INDEX&v=VERSION&p=KEY

With the information from the previous steps, we can append the information and send this link to a user.

KeyValue

c

ChainId expressed numerically for chain contract was called on. For example, in the contract call referenced above, this was made on Optimism mainnet. Therefore the c value would be 10.

i

Index slot of deposit. From the event above we know this is 0

v

Version identifying the contract. The version is defined as a string with the format vXXX in the SDK. Reference the registry file to look up the correct value for the contract and chain in question.

p

Key generated in step 1 for the ECDSA key pair.

Alternatively, we can simply store at minimum the following information to be able to claim in the future:

  • Chain and Contract Address

  • Deposit Index

  • Key used to generate ECDSA Key Pair

When claiming a Link, the following information needs to be provided to the contract:

  • Index slot of the deposit

  • Destination address to send funds

  • Hash and signature of destination address signed with the key pair used when depositing

The order of these parameters are clearly identified in the withdrawDeposit contract function signature.

    function withdrawDeposit(
        uint256 _index,
        address _recipientAddress,
        bytes32 _recipientAddressHash,
        bytes memory _signature
    ) external nonReentrant returns (bool) {
        // Function for withdrawing a deposit to a recipient.
        // ...
    }

The hashed recipient address is expected to be encoded according to EIP191 with the appropriate prefix. As an example, the SDK uses the following function to generate the hash of the address.

function solidityHashBytesEIP191(bytes) {
    return ethers.utils.hashMessage(bytes)
}

Then the signature is generated by signing the the hashed address using the private key generated prior when creating the ECDSA key pair from the random seed. For testing, make sure your hash and signature values match the following when using the test address 0xF4CF81931bb32E41CfdB24Ba10492C019A5ec5f9.

recipientAddress:  0xF4CF81931bb32E41CfdB24Ba10492C019A5ec5f9
addressHash:  0xed38f27bd5dfeb28a8fb1b0aecb00b86291e020d69825021c0320ede88434902
addressHashEIP191:  0xaef76579ddfee326849c76236d0aafce11a58f44b00b08cf3d0077c494a31657
signature:  0x8926942ac2254dcbd8d14ded150454a18d77d2e7073a8e4a663128fe33ed4c5d63e0617ccd1f80c8f754933a2cdad255542180c7be7f04eaeca40d9d1ead5d651c

When a Link has been successfully claimed, a WithdrawEvent event will be emitted from the contract call. The WithdrawEvent event will confirm the transfer alongside the funds that are sent to the recipient address.

event WithdrawEvent(
    uint256 indexed _index,
    uint8 indexed _contractType,
    uint256 _amount,
    address indexed _recipientAddress
);

Not to be confused with withdrawing funds from a Link (i.e. claiming a link). This process allows the original depositor / creator of the Link to retract the funds and cancel.

Any Link can be cancelled after 24 hours of creation by the original address that created it

To cancel and reclaim the funds for a Link that have not been claimed, call the function withdrawDepositSender with the index slot parameter.

   function withdrawDepositSender(uint256 _index)
      external nonReentrant returns (bool) {
        // Function for allowing a sender to withdraw their deposit.
        // ...
    }

Last updated