Transfer rights

Once an asset has been registered, the owner can trade it by creating a transfer record that points back to the original issue record (or to a previous transfer record) and that lists the new owner of the asset. Because the blockchain is ordered and because it’s immutable, this creates a permanent chain of custody reaching back to the asset’s origins.

There are two types of transferring a bitmark from one Bitmark account to another:

  • One-signature transfer: only requires the sender’s signature
  • Two-signature transfer: requires both the sender’s and the recipient’s signature

The one-signature transfer is similar to sending emails: the sender does not get consent from the recipient before sending the mail.

The two-signature transfer is similar to certified mail or a package delivery that requires a signature: the recipient has the right to accept or reject the delivery of a package. The actual transfer won’t take effect until the recipient explicitly provides the second signature, a.k.a. countersignature, as consent.

Bitmark owners can transfer their bitmarks to others using the SDK and the CLI.

Prerequisites

Using the SDK

Using the CLI

Transfer bitmarks using the SDK

Each bitmark transfer requires the same transaction fee to be executed. SDK users pay for this transaction fee via the SDK credit system.

Execute a one-signature transfer

A one-signature transfer is initiated and executed only by the sender.

  • //Import the sender account
    let sender = Account.fromSeed("your_account_seed");
    
    //Build and sign the transfer request
    let params = Bitmark.newTransferParams(recipientAccountNumber);
    await params.fromBitmark(bitmarkId); // asynchronous, just to check the head_id
    // params.fromTxId(latestTxId); // or synchronous
    params.sign(sender);
    
    //Submit the transfer
    let response = await Bitmark.transfer(params);
    
  • // Import the sender account
    sender = Account(fromSeed:"your_account_seed");
    
    // Build and sign the transfer request
    var params = try Bitmark.newTransferParams(to: recipientAccountNumber)
    try params.from(bitmarkID: bitmarkId)
    try params.sign(sender)
    
    // Submit the transfer
    let txId = try Bitmark.transfer(params)
    
  • // Import the sender account
    Account sennder = Account.fromSeed("your_account_seed");
    
    // Get the link from bitmarkId
    // You can use traditional callback or modern await
    // Use await for better code but don't forget to catch exceptions maybe occur
    BitmarkRecord bitmark = await((Callable1<GetBitmarkResponse>) callback -> Bitmark.get(bitmarkId, callback)).getBitmark();
    String link = bitmark.getHeadId();            
    
    // Build and sign the transfer request
    TransferParams params = new TransferParams(recipientAccountNumber, link);
    params.sign(sender);
    
    //Submit the transfer
    Bitmark.transfer(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
  • // Import the sender account
    sender, err := account.FromSeed("your_account_seed");
    
    // Build and sign the transfer request
    params, _ := bitmark.NewTransferParams(recipientAccountNumber)
    params.FromBitmark(bitmarkId)
    params.Sign(sender)
    
    // Submit the transfer
    txID, err := bitmark.Transfer(params)
    

Execute a two-signature transfer

For some scenarios, the sender wants to get a permission from the recipient before transferring a property. In the case, the sender initiates a two-signature transfer.

Propose a transfer offer

The sender initiates a two-signature transfer by proposing a transfer offer

  • // Import the sender account
    let sender = Account.fromSeed("your_account_seed");
    
    // Build and sign the transfer offer
    let params = Bitmark.newTransferOfferParams(recipientAccountNumber);
    await params.fromBitmark(bitmarkId); // asynchrous, just to check the head_id
    // params.fromTxId(lastestTxId); // or synchrous
    params.sign(senderAccount);
    
    // Submit the transfer offer
    let response = await Bitmark.offer(params);
    
  • // Import the sender account
    sender = Account(fromSeed:"your_account_seed");
    
    // Build and sign the transfer offer
    var params := Bitmark.newOfferParams(to: recipientAccountNumber, info: nil) // info: extra info attach to the transfer offer, it can be nil
    try params.from(bitmarkID: bitmarkId)
    try params.sign(account)
    
    // Submit the transfer offer
    try Bitmark.offer(withOfferParams: params)
    
  • // Import the sender account
    Account sennder = Account.fromSeed("your_account_seed");
    
    // Get the link from bitmarkId
    // You can use traditional callback or modern await
    // Use await for better code but don't forget to catch exceptions maybe occur
    BitmarkRecord bitmark = await((Callable1<GetBitmarkResponse>) callback -> Bitmark.get(bitmarkId, callback)).getBitmark();
    String link = bitmark.getHeadId();            
    
    // Build and sign the transfer offer
    TransferOfferParams params = new TransferOfferParams(recipient, link);
    params.sign(senderKey);
    
    // Submit the transfer offer
    Bitmark.offer(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
  • // Import the sender account
    sender, err := account.FromSeed("your_account_seed");
    
    // Build and sign the transfer offer
    params, _ := bitmark.NewOfferParams(recipientAccountNumber, nil)
    params.FromBitmark(bitmarkId)
    params.Sign(sender)
    
    // Submit the transfer offer
    err := bitmark.Offer(params)
    


Accept a transfer offer

If the receiver decides to accept the bitmark transfer offer, they generate a countersignature, and the transfer action takes effect.

The status of the bitmark will change from offering to transferring.

  • // Import the recipient account
    let recipient = Account.fromSeed("your_account_seed");
    
    // Build and sign the response
    let transferOfferResponseParams = Bitmark.newTransferResponseParams(BITMARK_CONSTANTS.TRANSFER_OFFER_RESPONSE_TYPES.ACCEPT);
    await transferOfferResponseParams.fromBitmark(bitmark.id);  // asynchrous, just to get offer from Bitmark
    // transferOfferResponseParams.fromOffer(offer) // or synchrous
    transferOfferResponseParams.sign(recipientAccount);
    
    // Submit the response
    response = await Bitmark.response(transferOfferResponseParams, recipientAccount);
    
    
  • // Import the recipient account
    recipient = Account(fromSeed:"your_account_seed");
    
    // Build and sign the response
    var responseParams = try Bitmark.newTransferResponseParams(withBitmark: receivingBitmark, action: .accept)
    try responseParams.sign(recipientAccount)
    
    // Submit the response
    try Bitmark.response(withResponseParams: responseParams)
    
    
  • // Import the recipient account
    Account recipient = Account.fromSeed("your_account_seed");
    
    // Build and sign the response
    TransferResponseParams params = TransferResponseParams.accept(offerRecord);
    params.sign(recipient);
    
    // Submit the response
    Bitmark.respond(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    
  • // Import the recipient account
    recipient, err := account.FromSeed("your_account_seed");
    
    // Build and sign the response
    bmk, _ := bitmark.Get(bitmarkId)
    params := bitmark.NewTransferResponseParams(bmk, bitmark.Accept)
    params.Sign(recipient)
    
    // Submit the response
    response, err := bitmark.Respond(params)
    
    


Reject a transfer offer

The receiver can also reject a bitmark transfer offer. The status of the bitmark will reverted to settled, and the sender can create a new transfer offer.

  • // Import the recipient account
    let recipient = Account.fromSeed("your_account_seed");
    
    // Build and sign the response
    let transferOfferResponseParams = Bitmark.newTransferResponseParams(BITMARK_CONSTANTS.TRANSFER_OFFER_RESPONSE_TYPES.REJECT);
    await transferOfferResponseParams.fromBitmark(bitmark.id);  // asynchrous, just to get offer from Bitmark
    // transferOfferResponseParams.fromOffer(offer) // or synchrous
    transferOfferResponseParams.sign(recipientAccount);
    
    // Submit the response
    response = await Bitmark.response(transferOfferResponseParams, recipientAccount);
    
    
  • // Import the recipient account
    recipient = Account(fromSeed:"your_account_seed");
    
    // Build and sign the response
    var responseParams = try Bitmark.newTransferResponseParams(withBitmark: receivingBitmark, action: .reject)
    try responseParams.sign(recipientAccount)
    
    // Submit the response
    try Bitmark.response(withResponseParams: responseParams)
    
    
  • // Import the recipient account
    Account recipient = Account.fromSeed("your_account_seed");
    
    // Build and sign the response
    TransferResponseParams params = TransferResponseParams.reject(offerRecord);
    params.sign(recipient);
    
    // Submit the response
    Bitmark.respond(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    
  • // Import the recipient account
    recipient, err := account.FromSeed("your_account_seed");
    
    // Build and sign the response
    bmk, _ := bitmark.Get(bitmarkId)
    params := bitmark.NewTransferResponseParams(bmk, bitmark.Reject)
    params.Sign(recipient)
    
    // Submit the response
    response, err := bitmark.Respond(params)
    


Cancel a transfer offer

If the recipient hasn’t responded to a transfer offer (neither accepted nor rejected it), the sender can cancel the offer.

Similar to the case of the recipient rejecting the offer, the status of the bitmark will be set to settled again, and the bitmark becomes available for a new transfer.

  • // Import the sender account
    let sender = Account.fromSeed("your_account_seed");
    
    // Build, sign and submit the cancellation
    let transferOfferResponseParams = Bitmark.newTransferResponseParams(BITMARK_CONSTANTS.TRANSFER_OFFER_RESPONSE_TYPES.CANCEL);
    await transferOfferResponseParams.fromBitmark(bitmark.id);  // asynchronous, just to get offer from Bitmark
    // transferOfferResponseParams.fromOffer(offer) // or synchronous
    response = await Bitmark.response(transferOfferResponseParams, sender);
    
  • // Import the sender account
    sender = Account(fromSeed:"your_account_seed");
    
    //Build and sign the cancellation
    var responseParams = try Bitmark.newTransferResponseParams(withBitmark: bitmark, action: .cancel)
    try responseParams.sign(sender)
    
    // Submit the cancellation
    try Bitmark.response(withResponseParams: responseParams)
    
  • // Import the sender account
    Account sennder = Account.fromSeed("your_account_seed");
    
    // Build and sign the cancellation
    TransferResponseParams params = TransferResponseParams.cancel(offerRecord, sender);
    params.sign(senderKey);
    
    //Submit the cancellation
    Bitmark.respond(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
  • // Import the sender account
    sender, err := account.FromSeed("your_account_seed");
    
    // Build and sign the cancellation
    bmk, _ := bitmark.Get("YOUR BITMARK ID")
    sender, _ := account.FromSeed("USER_A_SEED")
    params := bitmark.NewTransferResponseParams(bmk, bitmark.Cancel)
    params.Sign(sender)
    
    // Submit the cancellation
    _, err := bitmark.Respond(params)
    


Query transfer offers

A user is able to query to check if some transfer offers are existing with some certain conditions. For the details of query execution, please refer to the SDK Query.

  • let bitmarkQueryParams = Bitmark.newBitmarkQueryBuilder()
        .offerFrom("e1pFRPqPhY2gpgJTpCiwXDnVeouY9EjHY6STtKwdN6Z4bp4sog")
        .limit(10)
        .build();
    
    let response = await Bitmark.list(bitmarkQueryParams);
    
  • let query = try Bitmark.newBitmarkQueryParams()
        .limit(size: 100)
        .offer(from: "e1pFRPqPhY2gpgJTpCiwXDnVeouY9EjHY6STtKwdN6Z4bp4sog")
    let bitmarks = try Bitmark.list(params: query)
    
  • BitmarkQueryBuilder builder = new BitmarkQueryBuilder().offerFrom("e1pFRPqPhY2gpgJTpCiwXDnVeouY9EjHY6STtKwdN6Z4bp4sog");
    Bitmark.list(builder, new Callback1<GetBitmarksResponse>() {
                @Override
                public void onSuccess(GetBitmarksResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
  • builder := bitmark.NewQueryParamsBuilder().OfferTo("YOUR RECIPIENT ACCOUNT NUMBER")
    bitmarks, referencedAssets, err := bitmark.List(builder)
    

Transfer bitmarks using the CLI

The Bitmark CLI allows users to transfer bitmarks by submitting the transactions to its connected node and then broadcasting to the network. The CLI users pay for their transactions by sending BTC or LTC to the indicated addresses.

  • # Submit a transfer request
    $ bitmark-cli -n <network> -i <sender identity> transfer -r <recipient> -t <txId> -u
    
    # Run the bitcoind
    $ bitcoind -datadir=<bitcoind config dir>
    # OR run the litecoind
    $ litecoind -datadir=<litcoind config dir>
    #Pay the transfer fee by BTC
    $ bitmark-wallet --conf <Bitmark-Wallet config file> btc --<btc network> sendmany --hex-data '<payId>' '<btc address>,<btc amount in satoshi>'
    #OR Pay the transfer fee by LTC
    $ bitmark-wallet --conf <Bitmark-Wallet config file> ltc --<ltc network> sendmany --hex-data '<payId>' '<ltc address>,<ltc amount in photon>'
    
    # Check the transfer transaction status
    $ bitmark-cli -n <network> status -t <transferId>
    
    
    # Verify the bitmark provenance
    $ bitmark-cli -n <network> provenance -t <transferID>
    
    
  • # Submit a transfer request
    $ bitmark-cli -n testing -i first transfer -r second -t 6b35dfb5d623f6cae22fd03b3e28f1fde5255a29c1328a5d39ddfdfcd0ce6cf9 -u
    
    # run litecoind
    $ litecoind -datadir=~/.config/litecoin/
    # Pay the transfer fee
    $ bitmark-wallet --conf ~/.config/bitmark-wallet/test/test-bitmark-wallet.conf ltc --testnet sendmany --hex-data 'd819cff364b9211093fe09c2b462bdd05154472a72fac91a882a8f1129674dc92ac5d2724c8d26b16d414de8fbc5c62e' 'mzkCaHJmu1gdnsL9jxW2bwqtw2MCCy66Ds,200000'
    
    # Check the transfer transaction status
    $ bitmark-cli -n testing status -t 4656604222152d08606d42a40c8590c2100c177cafe374ea90bae30c5bd371e0
    
    
    # Verify the bitmark provenance
    $ bitmark-cli -n testing provenance -t 4656604222152d08606d42a40c8590c2100c177cafe374ea90bae30c5bd371e0
    
    
  • // Submit a transfer request
    /*=========================*/
    {
      "transferId": "4656604222152d08606d42a40c8590c2100c177cafe374ea90bae30c5bd371e0",
      "bitmarkId": "7e3337ae7596864e6dfd918c07780480ef80cea96fa039ed17d35c3849fcb3ca",
      "payId": "d819cff364b9211093fe09c2b462bdd05154472a72fac91a882a8f1129674dc92ac5d2724c8d26b16d414de8fbc5c62e",
      "payments": {
        "BTC": [
          {
            "currency": "BTC",
            "address": "mr8DEygRvQwKfP4sVZuHVozqvzW89e193j",
            "amount": "20000"
          }
        ],
        "LTC": [
          {
            "currency": "LTC",
            "address": "mzkCaHJmu1gdnsL9jxW2bwqtw2MCCy66Ds",
            "amount": "200000"
          }
        ]
      }
    },
    "commands": {
      "BTC": "bitmark-wallet --conf ${XDG_CONFIG_HOME}/bitmark-wallet/test/test-bitmark-wallet.conf btc --testnet sendmany --hex-data 'd819cff364b9211093fe09c2b462bdd05154472a72fac91a882a8f1129674dc92ac5d2724c8d26b16d414de8fbc5c62e' 'mnTuuYNZmmswFT8iqr7ex82HAQYLJ8LXkC,10000' 'mr8DEygRvQwKfP4sVZuHVozqvzW89e193j,20000'",
      "LTC": "bitmark-wallet --conf ${XDG_CONFIG_HOME}/bitmark-wallet/test/test-bitmark-wallet.conf ltc --testnet sendmany --hex-data 'd819cff364b9211093fe09c2b462bdd05154472a72fac91a882a8f1129674dc92ac5d2724c8d26b16d414de8fbc5c62e' 'mzkCaHJmu1gdnsL9jxW2bwqtw2MCCy66Ds,200000'"
    }
    
    // Pay the transfer fee
    /*====================*/
    {
      "txId": "ca94ae188ba8bfdc42e026950c5e13a2f1082dae484a45c5dc29217ac0c9a23f",
      "rawTx": "0100000001b76a37054a086c5bd68afd61914bb4badc78c9e7ef59e6b692777cc18063632d020000006b483045022100db6f27ec3e1e59c34887f262217d5ff819947c561f0ecd11034ba8b32dbdc87002203ef82d80be8e43434eb16e22248e4533a1d3c2831e19802ce55a308604d76f3c012103b45a55c3e48209581d63ba5ceea9a0e94ae49e18056d85a6dadec535dbe237a2ffffffff03400d0300000000001976a914d2ebb7b259fb7410dca19b707c4091195d818ac488ac8091e305000000001976a9142d477753d17099534f9249b54cda36081d4e5eba88ac0000000000000000326a30d819cff364b9211093fe09c2b462bdd05154472a72fac91a882a8f1129674dc92ac5d2724c8d26b16d414de8fbc5c62e00000000"
    }
    
    // Check the transfer transaction status right after the payment
    /*=============================================================*/
    {
      "status": "Verified"
    }
    // Check the transfer transaction status after some minutes
    {
      "status": "Confirmed"
    }
    
    // Verify the bitmark provenance
    /*=============================*/
    {
      "data": [
        {
          "record": "BitmarkTransferUnratified",
          "isOwner": true,
          "txId": "4656604222152d08606d42a40c8590c2100c177cafe374ea90bae30c5bd371e0",
          "inBlock": 31101,
          "data": {
            "escrow": null,
            "link": "7e3337ae7596864e6dfd918c07780480ef80cea96fa039ed17d35c3849fcb3ca",
            "owner": "fPWWkW45o12er6oP4EveaURHXstkSXR3odWCgpaDvEvxoR3woC",
            "signature": "1d378c7e...7dafcc02"
          },
          "_IDENTITY": "second"
        },
        {
          "record": "BitmarkIssue",
          "isOwner": false,
          "txId": "7e3337ae7596864e6dfd918c07780480ef80cea96fa039ed17d35c3849fcb3ca",
          "inBlock": 31100,
          "data": {
            "assetId": "813b2eb5...de9219a7",
            "nonce": 0,
            "owner": "fUuNhZ6CC4YxUkQB99nuLnUiEevEuwdCoYszJ9Y5uUjp8oiA3A",
            "signature": "4581ee97...9b24f20f"
          },
          "_IDENTITY": "first"
        },
        {
          "record": "AssetData",
          "isOwner": false,
          "inBlock": 31100,
          "assetId": "813b2eb5...de9219a7",
          "data": {
            "fingerprint": "01cdb27c...50514f0c",
            "metadata": "Key1\u0000Value1\u0000Key2\u0000Value2",
            "name": "Example asset 3",
            "registrant": "fUuNhZ6CC4YxUkQB99nuLnUiEevEuwdCoYszJ9Y5uUjp8oiA3A",
            "signature": "d6cc18e5...47efe909"
          },
          "_IDENTITY": "first"
    }
    

For the details of CLI commands and their parameters, please refer to the CLI command reference.

Explore the transactions using the Registry website

Users can explore all of the transactions on the Bitmark blockchain using the Bitmark Registry website:

References

CLI

SDK