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.
Peanut Protocol ContractsCreating a Link
To create a Link there are a few steps required.
- Generate the seed and derive asymmetric key pair from ID.
- Call the smart contract to deposit funds.
- 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.
plain textfunction 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.Param | Description |
_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 |
plain textfunction makeDeposit( address _tokenAddress, uint8 _contractType, uint256 _amount, uint256 _tokenId, address _pubKey20 ) public payable nonReentrant returns (uint256) { // Function for making a deposit. // ... }
Step 3: Creating the Link
Once the transaction calls
makeDeposit
successfully, it will emit a log confirming the deposit and index slot where the funds are held.plain textevent 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.
The photo shows an example of deposit event.
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:
plain texthttps://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.
Key | Value |
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
Claiming a Link
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.
plain textfunction 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.
plain textfunction 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.
plain textrecipientAddress: 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.plain textevent WithdrawEvent( uint256 indexed _index, uint8 indexed _contractType, uint256 _amount, address indexed _recipientAddress );
Withdrawing a Link
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.plain textfunction withdrawDepositSender(uint256 _index) external nonReentrant returns (bool) { // Function for allowing a sender to withdraw their deposit. // ... }