Claiming mainchain ZEN
This section covers the steps to follow to claim old mainchain ZEN balances after the migration.
Who needs to execute the manual claim?
Only ZEN balances coming from the old ZEND Mainchain will need to be manually claimed.
If you only had funds on EON Chain, your balances will be automatically moved to the same address in the new chain, and no manual operation will be needed.
Why is the manual claim needed?
A simple manual claim of the funds is required because the address format on the two chains is different (Bitcoin-format in the old Horizen chain, Ethereum format in the new Horizen chain). The on-chain UTXO structure does not track the original key/address owning it, making it impossible to automatically map between old and new addresses.
How to claim
The old mainchain is a Bitcoin-like chain, where funds are locked in multiple cryptographic "boxes" called UTXO.
To unlock funds, you will need to generate a signature of a specific message with the same private key able to "unlock" the corresponding UTXOs.
Two additional methods are developed to allow a "direct" claim that doesn't need any signed message.
The claim will then be performed on-chain, by calling a method on the official Horizen migration contract.
Here the details about the method call:
Pay-to-pub-key hash Claim
This is the most common format of UTXO, used for example by the Horizen Sphere Wallet and many other non-custodial wallets.
The Solidity method to claim this kind of UTXOs is the following:
function claimP2PKH(address destAddress,
bytes memory hexSignature,
PubKey calldata pubKey) public
Parameters details:
- destAddress: Destination address on Base of the funds to be claimed.
This can be generated by any keypair (using the same previous private-key is not mandatory), and can be different from the tx caller.
- hexSignature: ECDSA/Secp256k1 Signature generated with the private key associated with the UTXOs to claim, of the following message:
“ZENCLAIM” + destAddress
(destAddress here is represented in the hex form according to EIP-55, with “0x” prefix. No space has to be inserted after “ZENCLAIM” string). - pubKey: public key associated to the same private key used for the signature and owning the UTXOs. Must be always sent in uncompressed form: the PubKey data structure is composed of two bytes32 fields, that represents two components x and y (first 32 bytes and second 32 bytes).
Events emitted:
After a successful claim the following event will be emitted:
event Claimed(address destAddress, bytes20 zenAddress, uint256 amount)
Pay-to-script-hash Multisig Claim
This kind of UTXO is associated to multi-signature wallets. Note: we do not support any other type of Pay-to-script-hash different from the Multisig one.
The Solidity method to claim this kind of UTXOs is the following:
function claimP2SH(address destAddress,
bytes[] memory hexSignatures,
bytes memory script,
PubKey[] calldata pubKeys)
Parameters details:
- destAddress: Destination address on Base of the funds to be claimed. This can be generated by any keypair (using the same previous private key is not mandatory), and can be different from the tx caller.
- hexSignatures[]: List of ZEND ECDSA/Secp256k1 Signatures: Generated with the private keys associated to the multisig address, of the following message:
“ZENCLAIM”+ zen_multisig_address + destAddress:- zen_multisig_address: hex representation of zen multisig address derived from this script (base58check decoded representation without chain prefix (leading 2 bytes removed, so in total 20 bytes) prepended with "0x" prefix)
- destAddress: destination address represented in the hex form according to EIP-55, with “0x” prefix.
- No space has to be inserted between the three parts of the string.
- The minimum amount of valid signatures defined in the multisig script must be satisfied (example: 3 out of 5).
- The list size has always to be equal to the total number of signatures accepted by the script, and in case a signature is not present the corresponding element should be set to a 0 bytes array. For example: if the scripts accepts 2 out of 3 signatures, and we have only A and C signatures, the hexSignatures list will be: [SigA, 0, SigC]
- script: full UTXO redeem script.
- pubKeys: List of public keys accepted by the script. Must be always sent in uncompressed form: the PubKey data structure is composed of two bytes32 fields, that represents two components x and y (first 32 bytes and second 32 bytes).
The array length and position of elements must correspond to those defined in the script and to the array of signatures.
If a key signature is not present, also the corresponding pubKeys X and Y must both be bytes32(0)
Events emitted:
After a successful claim the following event will be emitted:
event Claimed(address destAddress, bytes20 zenAddress, uint256 amount)
Direct Claim - 1st method
This method allows a direct claim of special UTXOs generated deterministically from a BaseAddress.
This is a special usecase for users that can't generate a signed message, and requires they create this special UTXO in the old mainchain before the migration.
This method can be invoked by anyone (not mandatory the sender of the claim tx to be the same destination address).
The Solidity method to execute this claim is the following:
function claimDirect(address baseDestAddress) public
Parameters details:
- baseDestAddress: Destination address on Base of the funds to be claimed.
Any Base address is a valid baseDestAddress, and that address could claim the funds for the Zend Address generated as such:
- Calculate SHA256 hash of the baseDestAddress hex
- Calculate Ripemd160 hash of the output from step 1
- Concatenate prefix:
0x2089
for ZEND Mainnet or0x2098
for ZEND testnet - Encode it in Base 58
The procedure could be resumed by the formula:
base58.encode(‘0x2089’ + Ripemd160(SHA256(baseDestAddress hex)))
An example Javascript implementation is the following:
const createHash = require('create-hash')
const bs58check = require('bs58check')
const prefix = '2089'
const baseDestAddress = //Base address in string form without '0x' prefix
const ZENDTransferAddress = bs58check.encode(
Buffer.from(
prefix +
createHash('rmd160').update(
createHash('sha256').update(
Buffer.from(baseDestAddress, 'hex')
).digest()
).digest('hex'),
'hex')
)
console.log(ZENDTransferAddress)
The owner of an arbitrary Zend address should migrate its funds on the Zend address generated in this way BEFORE the Zend Migration.
As example, we consider a Zend address owner preparing for the migration that wants to use the direct claim:
- He generates a Base wallet and gets its address, for example:
0x6ebacd4a2a48728e98aAAA101C59f2e0c57fA987
- He executes the code above with parameter
baseDestAddress = 6ebacd4a2a48728e98aAAA101C59f2e0c57fA987
. The output iszncwpByDSdYjCw3HipRY8MS5dRRsxSR7AGU
- Before the Zend Migration, he sends a transaction to move the ZEN from his original address to the generated one (
zncwpByDSdYjCw3HipRY8MS5dRRsxSR7AGU
) - After the migration, he (or anyone else) can invoke the method
claimDirect(0x6ebacd4a2a48728e98aAAA101C59f2e0c57fA987)
on the migration Smart Contract. - The ZEN balance will be restored as ZEN ERC-20 token balance on Base chain on the address
0x6ebacd4a2a48728e98aAAA101C59f2e0c57fA987
Events emitted:
After a successful claim the following event will be emitted:
event Claimed(address destAddress, bytes20 zenAddress, uint256 amount)
Direct Claim - 2nd method
This method allows a direct claim of special UTXOs generated deterministically from a BaseAddress.
This is a special usecase for users that can't generate a signed message, and requires they create this special UTXO in the old mainchain before the migration.
It is similar to the previous 'Direct Claim - 1st method', but here the UTXO is a P2SH 1-of-2 multisig, on which one of the public keys is calculated from the beneficiary Base address. The other public key is a real one, so it is possible for the user to remain in control of their funds on Zend using this owned key.
This method can be invoked by anyone (not mandatory the sender of the claim tx to be the same destination address).
The Solidity method to execute this claim is the following:
function claimDirectMultisig(bytes memory script, address baseDestAddress) public
Parameters details:
- script: P2SH redeem Script of the 1-of-2 multisig address to claim
- baseDestAddress: Destination address on Base of the funds to be claimed.
Any Base address is a valid baseDestAddress, and that address could claim the funds for the multisig Zend Address generated as such:
- Calculate SHA256 hash of the baseDestAddress hex
- Create a 1-of-2 multisig address using as second public key the hash calculated at Step 1 with "02" as prefix
An example Javascript implementation for the multisig Zend Address calculation is the following:
const zencashjs = require('zencashjs')
const bs58check = require('bs58check')
const createHash = require('create-hash')
const baseDestAddress = //Base address in string form without '0x' prefix
const directMultisigPubKey1 = //Insert any owned public key
const directMultisigPubKey2 = "02"+createHash('sha256').update(Buffer.from(baseDestAddress, 'hex')).digest('hex')
multisigScript = zencashjs.address.mkMultiSigRedeemScript([directMultisigPubKey1, directMultisigPubKey2], 1, 2);
const zenDirectMultisigAddress = zencashjs.address.multiSigRSToAddress(multisigScript);
console.log(multisigScript)
console.log(zenDirectMultisigAddress)
The owner of an arbitrary Zend address should migrate its funds on the multisig Zend address generated in this way before the migration, then invoke claimDirectMultisig
method on claim smart contract with multisigScript
and baseDestAddress
as parameters.
Events emitted:
After a successful claim the following event will be emitted:
event Claimed(address destAddress, bytes20 zenAddress, uint256 amount)