Atomic cross-chain swap between NEM public and private chain

Cross-chain swaps enable trading tokens between different blockchains, without using an intermediary party in the process.

This exchange of tokens will succeed atomically. If some of the actors do not agree, each of them will receive the locked tokens back after a determined amount of time.

When talking about tokens in NEM, we are actually referring to mosaics. Catapult enables atomic swaps through secret lock / secret proof transaction mechanism.


Alice and Bob want to exchange 10 alice tokens for 10 bob tokens. The problem is that they are not in the same blockchain: alice:token is defined in NEM public chain, whereas bob:token is only present in a private chain using catapult technology.


NEM’s private and future public chain share the SDK. You could implement atomic cross-chain swap between blockchains using different technologies if they permit the secret lock/proof mechanism.

One non-atomic solution could be:

  1. Alice sends 10 alice tokens to Bob (private chain)
  2. Bob receives the transaction
  3. Bob sends 10 bob tokens to Alice (public chain)
  4. Alice receives the transaction

However, they do not trust each other that much. Bob could decide his mosaics to Alice. Following this guide, you will see how to make this swap possible, trusting technology.


Let’s get into some code

Trading tokens directly from one blockchain to the other is not possible, due to the technological differences between them.

In case of NEM public and private chain, the same mosaic name could have a different definition and distribution, or even not exist. Between Bitcoin and NEM, the difference is even more evident, as each blockchain uses an entirely different technology.

Instead of transferring tokens between different chains, the trade will be performed inside each chain. The Secret proof / secret lock mechanism guarantees the token swap occurs atomically.


Atomic cross-chain swap between public and private network

For that reason, each actor involved should have at least one account in each blockchain.

const alicePublicChainAccount = Account.createFromPrivateKey('', NetworkType.MAIN_NET);
const alicePrivateChainAccount = Account.createFromPrivateKey('', NetworkType.MIJIN);

const bobPublicChainAccount = Account.createFromPrivateKey('', NetworkType.MAIN_NET);
const bobPrivateChainAccount = Account.createFromPrivateKey('', NetworkType.MIJIN);

const privateChainTransactionHttp = new TransactionHttp('http://localhost:3000');
const publicChainTransactionHttp = new TransactionHttp('http://localhost:3000');
  1. Alice picks a random number, called proof. Then, applies a Sha3-256 algorithm to it, obtaining the secret.
const random = crypto.randomBytes(10);
const proof = random.toString('hex');
const hash = sha3_256.create();
const secret = hash.update(random).hex().toUpperCase();
  1. Alice creates a secret lock transaction, including:
  • The mosaic and amount to be sent: 10 [520597229,83226871] (alice tokens)
  • The recipient address: Bob’s address in private chain
  • The secret: Hashed proof.
  • The amount of time in which funds can be unlocked: 96h
  • The network: Private Chain
const tx1 = SecretLockTransaction.create(
    new Mosaic(new MosaicId([520597229,83226871]), UInt64.fromUint(10)),
    UInt64.fromUint(96*3600/15), // assuming one block every 15 seconds

Once announced, this transaction will remain locked until someone discovers the proof that matches the secret. If after a determined period of time no one proved it, the locked funds will be returned to Alice.

  1. Alice signs and announces TX1 to the private chain.
const tx1Signed = alicePrivateChainAccount.sign(tx1);
    .subscribe(x => console.log(x),err => console.error(err));
  1. Alice can tell Bob the secret. Also, he could retrieve it directly from the chain.
  2. Bob creates a secret lock transaction TX2, which contains:
  • The mosaic and amount to be sent: 10 [2061634929,1373884888] (bob token)
  • A recipient address: Alice’s address in public chain
  • The secret that should be achieved to unlock the funds.
  • The amount of time in which funds can be unlocked: 84h
  • The network: Public Chain
const tx2 = SecretLockTransaction.create(
    new Mosaic(new MosaicId([2061634929,1373884888]), UInt64.fromUint(10)),
    UInt64.fromUint(84*3600/15), // assuming one block every 15 seconds


The amount of time in which funds can be unlocked should be a smaller time frame than TX1’s. Alice knows the secret, so Bob must be sure he will have some time left after Alice releases the secret.

  1. Once signed, Bob announces TX2 to the public chain.
const tx2Signed = bobPublicChainAccount.sign(tx2);
    .subscribe(x => console.log(x), err => console.error(err));
  1. Alice can announce the secret proof transaction TX3 on the public network. This transaction defines the encrypting algorithm used, the original proof and the secret. It will unlock TX2 transaction.
const tx3 = SecretProofTransaction.create(

const tx3Signed = alicePublicChainAccount.sign(tx3);
    .subscribe(x => console.log(x), err => console.error(err));
  1. The proof is revealed in the public chain. Bob does the same by announcing a secret proof transaction TX4 in the private chain.
const tx4 = SecretProofTransaction.create(

const tx4Signed = bobPrivateChainAccount.sign(tx4);
    .subscribe(x => console.log(x), err => console.error(err));

It is at that moment when Bob unlocks TX1 funds, and the atomic cross-chain swap concludes.

Is it atomic?

Consider the following scenarios:

  1. Bob does not want to announce TX2. Alice will receive her funds back after 94 hours.
  2. Alice does not want to swap tokens by signing Tx3. Bob will receive his refund after 84h. Alice will unlock her funds as well after 94 hours.
  3. Alice signs and announces TX3, receiving Bob’s funds. Bob will have time to sign TX4, as Tx1 validity is longer than Tx2.

The process is atomic but should be completed with lots of time before the deadlines.