NAV Navbar
javascript swift java go
  • Overview
  • Getting Started
  • Account
  • Register an asset
  • Issue bitmarks
  • Transfer a bitmark
  • Query
  • Account migration
  • Store Seed
  • Overview

    The Bitmark property system is an universal property system for conferring the same property rights to digital assets that have long existed for physical assets. This system records ownership claims for digital assets as digital property titles known as bitmarks on the Bitmark public blockchain. A digital asset can be any digital object, including files, applications, code, and data. In the digital world:

    digital property = digital asset + bitmark

    The Bitmark SDK enables creation, transfer, and authentication of digital properties in the Bitmark property system. The SDK's simplified interface allows developers to easily build on the core Bitmark infrastructure by reading from and writing to the Bitmark blockchain.

    Getting Started

    Installation

    npm install bitmark-sdk
    
    pod 'BitmarkSDK'
    
    // From your build.gradle
    repositories {
        jcenter()
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } // For snapshot version
    }
    
    dependencies {
        implementation 'com.bitmark.sdk:java-sdk:1.5' // Java SDK
        // Or
        implementation 'com.bitmark.sdk:android-sdk:1.4' // Android SDK
    }
    
    // Please specify to use v2 branch by dependency management tool, e.g.,
    govendor fetch github.com/bitmark-inc/bitmark-sdk-go@v2
    
    // Otherwise, checkout to v2 branch manually
    go get github.com/bitmark-inc/bitmark-sdk-go
    cd $GOPATH/src/github.com/bitmark-inc/bitmark-sdk-go
    git checkout v2
    

    Get your API token

    The API token is required to authenticate your requests to the Bitmark API.

    Please contact our support to create your developer account and get the API token.

    Initialize

    Configuration

    const sdk = require('bitmark-sdk');
    
    const config = {
      API_token: "api-token",
      network: "testnet"
    };
    
    sdk.init(config);
    
    
    import BitmarkSDK
    
    BitmarkSDK.initialize(config: SDKConfig(apiToken: "api-token",
                                            network: .testnet,
                                            urlSession: URLSession.shared))
    
    final GlobalConfiguration.Builder builder = GlobalConfiguration.builder().withApiToken("api-token").withNetwork(Network.LIVE_NET);
    BitmarkSDK.init(builder);
    
    
    BitmarkSDK.init("api-token");
    
    import sdk "github.com/bitmark/bitmark-inc/bitmark-sdk-go"
    
    func main() {
      // Go to https://github.com/bitmark-inc/bitmark-sdk-go/config.go for details.
      httpClient := &http.Client{
        Timeout: 10 * time.Second,
      }
      config := &sdk.Config{
        APIToken: "api-token",
        Network: "testnet",
        HTTPClient: httpClient,
      }
      sdk.Init(config)
    
      // Every subsequent examples assumes the SDK is already correctly initialized
    }
    

    Account

    Within the Bitmark system, an account represents any entity capable of creating and owning property, whether individuals, insitutions, or organizations.

    An account incorporates the public-private keypair and the private key is required to digitally sign any Bitmark blockchain record, including asset records, issue records, and transfer records.

    Create an account

    let account = new sdk.Account();
    
    // or
    const Account = sdk.Account;
    let account = new Account();
    
    
    let account = try Account() // default will be version 1
    
    // or we can create bitmark account v2
    
    let account = try Account(version: .v2, network: .testnet)
    
    Account account = new Account();
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func createNewAccountExample() {
        account, err := account.New()
    }
    

    Get the account number

    let accountNumber = account.getAccountNumber();
    // ffzcoJeg7p6kJrV6VNhS6juuceTCKMmek1WrXopvbzNTvYqANy
    
    let accountNumber = account.accountNumber()
    // ffzcoJeg7p6kJrV6VNhS6juuceTCKMmek1WrXopvbzNTvYqANy
    
    String accountNumber = account.getAccountNumber();
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func getAccountNumberExample(acct account.Account) {
        accountNumber := acct.AccountNumber()
    }
    

    The account number of an account serves as a pseudonymous identifier within the Bitmark blockchain, which can represent:

    Export an account

    We provide two formats for exporting an account: seed and recovery phrase, both of which store all the required information to instantiate an account.

    Seed

    let seed = account.getSeed();
    // 9J87CAsHdFdoEu6N1unZk3sqhVBkVL8Z8
    
    let seed = try account.toSeed()
    // 5XEECttvVsk5xPjZ1zrgtWoauw2xmPwTKCWEN5GF24UpaGZhAGS6tXd
    
    Seed seed = account.getSeed();
    String encodedSeed = seed.getEncodedSeed();
    // 9J87CAsHdFdoEu6N1unZk3sqhVBkVL8Z8
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func getSeedExample(acct account.Account) {
        seed := acct.Seed()
    }
    

    The seed is the more compact format of an exported account for your program to re-instantiate an account.

    Recovery Phrase

    // English version
    let recoveryPhrase = account.getRecoveryPhrase();
    // or 
    let recoveryPhrase = account.getRecoveryPhrase("en");
    // "name gaze apart lamp lift zone believe steak session laptop crowd hill"
    
    // Chinese version
    let recoveryPhrase = account.getRecoveryPhrase("cn");
    // "箱 阻 起 归 彻 矮 问 栽 瓜 鼓 支 乐"
    
    // English version
    let phrase = try account.getRecoverPhrase(language: .english)
    
    // Tranditional Chinese version
    let phrase = try account.getRecoverPhrase(language: .chineseTraditional)
    
    // English ver
    RecoveryPhrase recoveryPhrase = account.getRecoveryPhrase();
    // Or
    RecoveryPhrase recoveryPhrase = account.getRecoveryPhrase(Locale.ENGLISH);
    
    String[] mnemonicWords = recoveryPhrase.getMnemonicWords();
    // ["name", "gaze", "apart", "lamp", "lift", "zone",
    //  "believe", "steak", "session", "laptop", "crowd", "hill"]
    
    // Chinese ver
    RecoveryPhrase recoveryPhrase = account.getRecoveryPhrase(Locale.CHINESE);
    String[] mnemonicWords = recoveryPhrase.getMnemonicWords();
    // ["箱", "阻", "起", "归", "彻", "矮", "问", "栽", "瓜", "鼓", "支", "乐"]
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "golang.org/x/text/language"
    )
    
    func getRecoveryPhraseExample(acct account.Account) {
        phrase, err := acct.RecoveryPhrase(language.AmericanEnglish)
    }
    

    The recovery phrase, which consists of 12 mnemonic words, is superior for human interaction compared to the handling of seed. If you don't plan to custody user's private key, make sure you present the recovery phrase to your user. Currently English and traditional Chinese are supported.

    Import an account

    On the contrast, there are functions for you to recover the accounts.

    Recover from seed

    let account = Account.fromSeed("9J87CAsHdFdoEu6N1unZk3sqhVBkVL8Z8");
    
    account = Account(fromSeed: "5XEECttvVsk5xPjZ1zrgtWoauw2xmPwTKCWEN5GF24UpaGZhAGS6tXd")
    
    // 12 words
    Seed seed = SeedTwelve.fromEncodedSeed("9J87CAsHdFdoEu6N1unZk3sqhVBkVL8Z8");
    
    // 24 words
    Seed seed = SeedTwentyFour.fromEncodedSeed("5XEECt18HGBGNET1PpxLhy5CsCLG9jnmM6Q8QGF4U2yGb1DABXZsVeD");
    
    Account account = Account.fromSeed(seed);
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func recoverFromSeedExample() {
        account, err := account.FromSeed("9J87CAsHdFdoEu6N1unZk3sqhVBkVL8Z8")
    }
    

    Recover from phrase

    // English version
    let account = Account.fromRecoveryPhrase("name gaze apart lamp lift zone believe steak session laptop crowd hill");
    // or
    let account = Account.fromRecoveryPhrase("name gaze apart lamp lift zone believe steak session laptop crowd hill", "en");
    
    
    // Chinese version
    let account = Account.fromRecoveryPhrase("箱 阻 起 归 彻 矮 问 栽 瓜 鼓 支 乐", "cn");
    
    // English version
    let account = try Account(recoverPhrase: [
        "music", "life", "stone", "brain", "slush", "mango",
         "diagram", "business", "dumb", "cinnamon", "coral", "year"],
        language: .english
    )
    
    // Tranditional Chinese version
    let account = try Account(recoverPhrase: [
        "婆", "潮", "睛", "毫", "壤", "殿", "北", "謝", "人", "答", "隊", "星"],
        language: .chineseTraditional
    )
    
    Account account = Account.fromRecoveryPhrase("name", "gaze", "apart", "lamp", "lift", "zone", "believe", "steak", "session", "laptop", "crowd", "hill"); // English
    
    // Or 
    Account account = Account.fromRecoveryPhrase("箱", "阻", "起", "归", "彻", "矮", "问", "栽", "瓜", "鼓", "支", "乐"); // Chinese
    
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func recoverFromPhraseExample() {
        account := account.FromRecoveryPhrase(
            []string{
                "name", "gaze", "apart", "lamp", " lift", " zone",
                "believe" , "steak", "session", "laptop", "crowd", "hill",
            },
            language.AmericanEnglish,
        )
    }
    

    Account utility functions

    Here are some are some helper functions.

    Validate account number

    let isValid = Account.isValidAccountNumber(accountNumber);
    
    let isValid = Account.isValidAccountNumber(accountNumber)
    
    boolean isValid = Account.isValidAccountNumber(accountNumber);
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func main() {
        valid := account.IsValidAccountNumber(accountNumber)
    }
    

    The function returns a boolean to indicate whether a given account number is valid in current runtime environment, i.e., the format is correct and its network matches to the network specified in the SDK config during initialization.

    Parse information from an account number

    let accountInfo = Account.parseAccountNumber(accountNumber);
    //{
    //  network: "livenet",
    //  pubKey: "6kJrV6VNhS6juuceTCKMmek1WrXopvbzNTvYqANy"
    //}
    
    let (network, pubkey) = try Account.parseAccountNumber(accountNumber)
    
    AccountNumberData accountNumberData = Account.parseAccountNumber(accountNumber);
    Network network = accountNumberData.getNetwork();
    byte[] publicKey = accountNumberData.getPublicKey().toBytes();
    
    import "github.com/bitmark-inc/bitmark-sdk-go/account"
    
    func main() {
        network, pubkey, err := account.ParseAccountNumber(accountNumber)
    }
    

    The function parses an account number and returns its network and public key.

    Register an asset

    let params = Asset.newRegistrationParams(assetName, metadata);
    await params.setFingerprint(filePath);
    params.sign(account);
    
    let response = await Asset.register(params);
    
    var params = try Asset.newRegistrationParams(name: "asset_name",
                                                metadata: ["desc": "sdk example"])
    
    let fileURL = Bundle.main.url(forResource: "file", withExtension: ".ext")!
    try params.setFingerprint(fromFileURL: fileURL)
    try params.sign(account)
    
    let assetId = try Asset.register(params)
    
    Map<String, String> metadata = new HashMap<>(){{
        put("name", "name");
        put("desc", "sdk_example");
    }};
    Address registrant = account.toAddress();
    RegistrationParams params = new RegistrationParams("asset_name", metadata, registrant);
    params.generateFingerprint(file);
    params.sign(key);
    Asset.register(params, new Callback1<RegistrationResponse>() {
                @Override
                public void onSuccess(RegistrationResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/asset"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func registerAssetExample(owner account.Account) {
        params, err := asset.NewRegistrationParams(
            "name", // asset name
            map[string]string{"k1": "v1", "k2": "v2"}, // asset metadata
        )
    
        dat, err := ioutil.ReadFile("/tmp/dat")
        params.SetFingerprint(dat) // calculate the fingerprint
    
        params.Sign(owner)
        params.JSON()
    
        assetId, err := asset.Register(p)
    }
    

    The first step to create a digital property is to register assets. An asset can any digital object, including files, applications, code, and data.

    Each asset is described by name and metadata (optional), and can be uniquely identified by its fingerprint.

    If an asset record with the same fingerprint value already exists in the blockchain, the new asset record is rejected from incorporation in the blockchain.

    Issue bitmarks

    This system records ownership claims for digital assets as digital property titles known as bitmarks.

    After the asset is registered, you can issue bitmarks with a permananent reference to the corresponding asset record.

    There can be multiple issues for the same asset, each defined by a different nonce, and each representing a different instance of the property.

    Create issuances with nonces

    let params = Bitmark.newIssuanceParams(assetId, nonces = [1, 2, ..., 100]);
    params.sign(account);
    
    let response = await Bitmark.issue(params);
    
    var params = Bitmark.newIssuanceParams(assetId: assetId,
                                           nonces: [1..100])
    try params.sign(issuer)
    
    let bitmarkIds = try Bitmark.issue(params)
    
    Address owner = account.toAddress();
    IssuanceParams params = new IssuanceParams(assetId, owner, new int[] {1, 2, 3, 4, 5});
    params.sign(ownerKey);
    Bitmark.issue(params, new Callback1<List<String>>() {
                @Override
                public void onSuccess(List<String> bitmarkIds) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func issueByNoncesExample(issuer account.Account) {
        params := bitmark.NewIssuanceParams(
            assetId,
            bitmark.QuantityOptions{
                Nonces: []uint64{uint64(1), uint64(2), uint64(3)},
            },
        )
        params.Sign(issuer)
        bitmarkIds, err := bitmark.Issue(params) // returns three bitmark IDs
    }
    

    Nonce is a counter value that distinguishes different issuances for the same asset of the same owner. Typically, these represent “limited editions” of a digital asset, and the nonce can be viewed as edition number.

    The combination of nonce, owner and asset should be unique over the blockchain. To issue more bitmarks, developers need to make sure there is no duplicated nonces for issuing within a same owner and asset pair.

    Create issuances without nonces

    let params = Bitmark.newIssuanceParams(assetId, quantity = 100);
    params.sign(account);
    
    let bitmarkIds = Bitmark.issue(params);
    
    var params = try Bitmark.newIssuanceParams(assetId: assetId,
                                                owner: issuer.accountNumber,
                                                quantity: 100)
    try params.sign(issuer)
    
    let bitmarkIds = try Bitmark.issue(params)
    
    Address owner = account.toAddress();
    IssuanceParams params = new IssuanceParams(assetId, owner, quantity);
    params.sign(ownerKey);
    Bitmark.issue(params, new Callback1<List<String>>() {
                @Override
                public void onSuccess(List<String> txIds) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func issueByQuantityExample(issuer account.Account) {
        params := bitmark.NewIssuanceParams(
            assetId,
            bitmark.QuantityOptions{
                Quantity: 3,
            },
        )
        params.Sign(issuer)
        bitmarkIds, err := bitmark.Issue(params) // returns three bitmark IDs
    }
    

    If you simply want to generate authorized copies of digital data on demand, you can set quantity instead. The SDK will generate random nonces automatically for issuing.

    Transfer a bitmark

    Bitmark transfer, which is the process of transferring bitmark ownership from one Bitmark account to another.

    There are two ways to transfer a bitmark:

    Direct transfer is similar to sending emails, the sender does not get the consent from the receiver before sending a mail.

    Countersigned transfer is similar to express delivery, the receiver has the right to accept or reject the delivery of a package. The actual transfer won't take effect until the receiver explicitly provides the second signature, a.k.a. countersignature, as the consent.

    Direct transfer

    let params = Bitmark.newTransferParams(receiverAccountNumber);
    await params.fromBitmark(bitmarkId); // asynchrous, just to check the head_id
    // params.fromTxId(lastestTxId); // or synchrous
    params.sign(account);
    
    let response = await Bitmark.transfer(params);
    
    var params = try Bitmark.newTransferParams(to: receiverAccountNumber)
    try params.from(bitmarkID: bitmarkId)
    try params.sign(account)
    
    let txId = try Bitmark.transfer(params)
    
    // 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();            
    
    // Transfer bitmark
    Address receiver = account.toAddress();
    TransferParams params = new TransferParams(receiver, link);
    params.sign(senderKey);
    Bitmark.transfer(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func transferExample(sender account.Account) {
        params := bitmark.NewTransferParams("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9") // set receiver's account number
        params.FromBitmark("71131367bc56628bb2eee15da274e466f2ae1533c192d60c9eeef6484b1117e3") // specify which bitmark to be transferred
    
        // In addition to specifying bitmark directly, you can also specify the latest tx ID of this bitmark
        // params.FromLatestTx("0374d3cd9a901d0c3d084c0e1d57b2c29331eafbbd183fa4fabb40eae331a3d7")
    
        params.Sign(sender)
        txId, err := bitmark.Transfer(params)
    }
    

    The sender can transfer a bitmark to another account without additional consent.

    Countersigned transfer

    For some scenario, the developer want to get a permission from the receiver before we transfer a property to it. In the case, you will submit a two-signature transfer.

    Propose a bitmark transfer offer

    let params = Bitmark.newTransferOfferParams(receiverAccountNumber);
    await params.fromBitmark(bitmarkId); // asynchrous, just to check the head_id
    // params.fromTxId(lastestTxId); // or synchrous
    params.sign(senderAccount);
    let response = await Bitmark.offer(params);
    
    var params := Bitmark.newOfferParams(to: receiverAccountNumber, info: nil) // info: extra info attach to the transfer offer, it can be nil
    try params.from(bitmarkID: bitmarkId)
    try params.sign(account)
    
    try Bitmark.offer(withOfferParams: params)
    
    // 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();            
    
    // Offer bitmark
    Address receiver = account.toAddress();
    TransferOfferParams params = new TransferOfferParams(receiver, link);
    params.sign(senderKey);
    Bitmark.offer(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func offerBitmarkExample(sender account.Account) {
        params := bitmark.NewOfferParams(receiverAccountNumber, true)
        params.FromBitmark() // asynchrous, just to check the head_id
        // params.FromTx() // or synchrous
        params.Sign(sender)
    
        bitmark.Offer(params)
    }
    

    The current owner of a bitmark can propose a transfer offer for another account if the status of the bitmark is settled, i.e. either the issue or the transfer transaction of this bitmark is already confirmed on the blockchain. The actual ownership transfer won't happen until the receiver accepts the offer.

    Query offering bitmarks

    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) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func listOfferingBitmarksExample() {
        builder := bitmark.NewQueryParamsBuilder().
            OfferFrom("e1pFRPqPhY2gpgJTpCiwXDnVeouY9EjHY6STtKwdN6Z4bp4sog").
            Limit(10)
    
        bitmarks, err := bitmark.List(builder)
    }
    

    The receiver needs to query if there is any bitmark transfer offer waiting for the countersignature. For the details of query execution, please refer to Query Bitmark.

    Accept the bitmark transfer offer

    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(receiverAccount);
    
    response = await Bitmark.response(transferOfferResponseParams, receiverAccount);
    
    var responseParams = try Bitmark.newTransferResponseParams(withBitmark: receivingBitmark, action: .accept)
    try responseParams.sign(receiverAccount)
    try Bitmark.response(withResponseParams: responseParams)
    
    TransferResponseParams params = TransferResponseParams.accept(offerRecord);
    params.sign(receiverKey);
    Bitmark.respond(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    If the receiver decides to accept the bitmark, the countersignature is generated and make the transfer action take effect. The status of the bitmark will change from offering to transferring. The

    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func acceptOfferExample(receiver account.Account) {
        params := bitmark.NewTransferResponseParams(bitmark, sdk.Bitmark.Accpet)
        params.Sign(receiver)
        txId, err := bitmark.Respond(params)
    }
    

    Reject the transfer offer

    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(receiverAccount);
    
    response = await Bitmark.response(transferOfferResponseParams, receiverAccount);
    
    var responseParams = try Bitmark.newTransferResponseParams(withBitmark: receivingBitmark, action: .reject)
    try responseParams.sign(receiverAccount)
    try Bitmark.response(withResponseParams: responseParams)
    
    TransferResponseParams params = TransferResponseParams.reject(offerRecord);
    params.sign(receiverKey);
    Bitmark.respond(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func rejectOfferExample(receiver account.Account) {
        params := bitmark.NewTransferResponseParams(bitmark, sdk.Bitmark.Reject)
        params.Sign(receiver)
        txId, err := bitmark.Respond(params)
    }
    

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

    Cancel the transfer offer

    let transferOfferResponseParams = Bitmark.newTransferResponseParams(BITMARK_CONSTANTS.TRANSFER_OFFER_RESPONSE_TYPES.CANCEL);
    await transferOfferResponseParams.fromBitmark(bitmark.id);  // asynchrous, just to get offer from Bitmark
    // transferOfferResponseParams.fromOffer(offer) // or synchrous
    
    response = await Bitmark.response(transferOfferResponseParams, senderAccount);
    
    var responseParams = try Bitmark.newTransferResponseParams(withBitmark: bitmark, action: .cancel)
    try responseParams.sign(senderAccount)
    try Bitmark.response(withResponseParams: responseParams)
    
    TransferResponseParams params = TransferResponseParams.cancel(offerRecord, sender);
    params.sign(senderKey);
    Bitmark.respond(params, new Callback1<String>() {
                @Override
                public void onSuccess(String txId) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/account"
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func cancelOfferExample(receiver account.Account) {
        params := bitmark.NewTransferResponseParams(bitmark, sdk.Bitmark.Cancel)
        params.Sign(receiver)
        txId, err := bitmark.Respond(params)
    }
    

    If the receiver hasn't responded to the bitmark transfer offer (neither accepted nor rejected), the sender can cancel the offer. Similar to the case of the receiver rejectting the offer, the status of the bitmark will be set to settled again, and becomes available for the next transfer.

    Query

    Asset

    Attribute Description
    id The asset ID
    name The asset name
    metadata The asset metadata, described by key-value paris
    fingerprint The hash value of the asset content, which serves as the unique identifier for the asset record in the blockchain
    registrant The account registering the asset
    status Possible values: pending, confirmed
    block_number The block which incorporates the asset record
    created_at When the asset status becomes confirmed

    Query for a specific asset

    let response = await Asset.get(assetId);
    
    let response = Asset.get(assetID: assetId)
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/asset"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func getAssetExample() {
        asset, err := asset.Get(assetId)
    }
    
    Asset.get(assetId, new Callback1<AssetRecord>() {
                @Override
                public void onSuccess(AssetRecord asset) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Query for a set of assets

    let assetQueryParams = Asset.newAssetQueryBuilder()
        .registeredBy("ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .pending(true)
        .limit(10)
        .build();
    
    let response = await Asset.list(assetQueryParams);
    
    let params = try Asset.newQueryParams()
                    .limit(size: 100)
                    .registeredBy(registrant: "ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
    
    let assets = try Asset.list(params: params)
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/asset"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func listAssetsExample() {
        params := asset.NewQueryParamsBuilder().
            RegisteredBy("ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva").
            Limit(10)
        assets, err := asset.List(params)
    }
    
    AssetQueryBuilder builder = new AssetQueryBuilder().limit(limit).registrant(registrant);
    Asset.list(builder, new Callback1<List<AssetRecord>>() {
                @Override
                public void onSuccess(List<AssetRecord> assets) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Bitmark

    Attribute Description
    id The bitmark ID
    asset_id The asset ID
    asset The asset record
    latest_tx_id The latest tx ID
    issuer The account issuing the bitmark
    owner The account currently owningthe bitmark
    offer See the offer attributes below.
    status Possible values: issuing, transferring, offering, settled See the following diagram for definition.
    block_number The block which incorporates the latest tx of this bitmark
    created_at When the bitmark is issued
    updated_at The last time when the bitmark is transferred

    Bitmark status diagram

    Offer

    Attribute Description
    id The offer ID
    from Represents the account creating the offer
    to Represents the account which can accept/reject the bitmark
    record The half-signed transfer tx
    extra_info Attached JSON message for indicating the details of this offer
    created_at The create time of the offer

    Query for a specific bitmark

    let response = await Bitmark.get(bitmarkId, false); // false: not include asset, true: include asset 
    
    let bitmark = Bitmark.get(bitmarkID: bitmarkId); 
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func getBitmarkExample() {
        bitmark, err := bitmark.Get(bitmarkId, false)
    }
    

    Query for a set of bitmarks

    let bitmarkQueryParams = Bitmark.newBitmarkQueryBuilder()
        .ownedBy("ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .issuedBy("ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .referencedAsset("0e0b4e3bd771811d35a23707ba6197aa1dd5937439a221eaf8e7909309e7b31b6c0e06a1001c261a099abf04c560199db898bc154cf128aa9efa5efd36030c64")
        .offerFrom("ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .offerTo("ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .loadAsset(true)
        .pending(true)
        .limit(10)
        .build();
    
    let response = await Bitmark.list(bitmarkQueryParams);
    
    let query = try Bitmark.newBitmarkQueryParams()
        .limit(size: 100)
        .issued(by: "ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .referenced(toAssetID: "0e0b4e3bd771811d35a23707ba6197aa1dd5937439a221eaf8e7909309e7b31b6c0e06a1001c261a099abf04c560199db898bc154cf128aa9efa5efd36030c64")
        .offer(from: "ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .offer(to: "ec6yMcJATX6gjNwvqp8rbc4jNEasoUgbfBBGGyV5NvoJ54NXva")
        .at(offset)
        .to(direction: .earlier)
        .loadAsset(true)
        .includePending(true)
    let (bitmarks, assets) = try Bitmark.list(params: query)
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/bitmark"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func listBitmarksExample() {
        params := bitmark.NewQueryParamsBuilder().
            IssuedBy("e1pFRPqPhY2gpgJTpCiwXDnVeouY9EjHY6STtKwdN6Z4bp4sog").
            OwnedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9", true).
            OfferTo("dzJjGazcRuC7KhgU5o2Y2YV8wGXhBBabGRACa2Uyg4ZkVWwyNu").
            OfferFrom("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9").
            ReferencedAsset("1f21148a273b5e63773ceee976a84bcd014d88ac2c18a29cac4442120b430e158386b0ad90515c69e7d1fd6df8f3d523e3550741e88d0d04798627a57b0006c9").
            LoadAsset(true).
            Limit(10)
    
        bitmarks, err := bitmark.List(params)
    }
    
    BitmarkQueryBuilder builder = new BitmarkQueryBuilder()
                                .issuedBy("e1pFRPqPhY2gpgJTpCiwXDnVeouY9EjHY6STtKwdN6Z4bp4sog")
                                .ownedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9")
                                .offerTo("dzJjGazcRuC7KhgU5o2Y2YV8wGXhBBabGRACa2Uyg4ZkVWwyNu")
                                .offerFrom("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9")
                                .referenceAsset("1f21148a273b5e63773ceee976a84bcd014d88ac2c18a29cac4442120b430e158386b0ad90515c69e7d1fd6df8f3d523e3550741e88d0d04798627a57b0006c9")
                                .loadAsset(true)
                                .limit(10);
    Bitmark.list(builder, new Callback1<GetBitmarksResponse>() {
                @Override
                public void onSuccess(GetBitmarksResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Tx

    Attribute Description
    id The tx ID
    bitmark_id Links to the bitmark which this tx is applied to
    asset_id The asset ID
    asset The asset record
    owner The account owning the bitmark
    status Possible values: pending, confirmed
    block_number The block which incorporates the tx record

    A new tx record is generated accordingly when there is an update to the bitmark ownership.

    Query for a specific transaction

    let txResponse = await Transaction.get(txId);
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/tx"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func getTxExample() {
        tx, err := tx.Get(txId, true)
    }
    
    Transaction.get(txId, new Callback1<GetTransactionResponse>() {
                @Override
                public void onSuccess(GetTransactionResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Query for a set of transactions

    let transactionQueryParams = Transaction.newTransactionQueryBuilder()
        .ownedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9")
        .referencedAsset("1f21148a273b5e63773ceee976a84bcd014d88ac2c18a29cac4442120b430e158386b0ad90515c69e7d1fd6df8f3d523e3550741e88d0d04798627a57b0006c9")
        .referencedBitmark("c8e021c1a093c32909e4d29b4624f8a5443e349a597314b7c9527ce310749121")
        .loadAsset(true)
        .limit(10)
        .build();
    
    let response = await Transaction.list(transactionQueryParams);
    
    let query = try Transaction.newTransactionQueryParams()
        .limit(size: 100)
        .owned(by: "eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9", transient: true)
        .referenced(toAssetID: "0e0b4e3bd771811d35a23707ba6197aa1dd5937439a221eaf8e7909309e7b31b6c0e06a1001c261a099abf04c560199db898bc154cf128aa9efa5efd36030c64")
        .referenced(toBitmarkID: "58737de5ad68a535da6277da62d11eb3ed76ff6dd7fc2adf3c42a4096d9a2518")
        .at(offset)
        .to(direction: .earlier)
        .loadAsset(true)
        .includePending(true)
    let (txs, assets) = try Transaction.list(params: query)
    
    import (
        "github.com/bitmark-inc/bitmark-sdk-go/tx"
    )
    
    // This sample assumes the SDK is already correctly initialized
    func listTxsExample() {
        params := tx.NewQueryParamsBuilder().
            OwnedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9", true).
            ReferencedAsset("0e0b4e3bd771811d35a23707ba6197aa1dd5937439a221eaf8e7909309e7b31b6c0e06a1001c261a099abf04c560199db898bc154cf128aa9efa5efd36030c64").
            ReferencedBitmark("58737de5ad68a535da6277da62d11eb3ed76ff6dd7fc2adf3c42a4096d9a2518").
            LoadAsset(true).
            Limit(10)
    
        txs, err := tx.List(params)
    }
    
    TransactionQueryBuilder builder = new TransactionQueryBuilder()
                                    .ownedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9")
                                    .referenceAsset("0e0b4e3bd771811d35a23707ba6197aa1dd5937439a221eaf8e7909309e7b31b6c0e06a1001c261a099abf04c560199db898bc154cf128aa9efa5efd36030c64")
                                    .referenceBitmark("58737de5ad68a535da6277da62d11eb3ed76ff6dd7fc2adf3c42a4096d9a2518")
                                    .loadAsset(true)
                                    .limit(10);
    Transaction.list(builder, new Callback1<GetTransactionsResponse>() {
                @Override
                public void onSuccess(GetTransactionsResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Query the provenance of a bitmark

    let transactionQueryParams = Transaction.newTransactionQueryBuilder()
        .referencedBitmark("c8e021c1a093c32909e4d29b4624f8a5443e349a597314b7c9527ce310749121")
        .build();
    
    let response = await Transaction.list(transactionQueryParams);
    
    let query = try Transaction.newTransactionQueryParams()
        .referenced(toBitmarkID: "58737de5ad68a535da6277da62d11eb3ed76ff6dd7fc2adf3c42a4096d9a2518")
    let (txs, _) = try Transaction.list(params: query)
    
    params := tx.NewQueryParamsBuilder().
        ReferencedBitmark("58737de5ad68a535da6277da62d11eb3ed76ff6dd7fc2adf3c42a4096d9a2518")
    
    TransactionQueryBuilder builder = new TransactionQueryBuilder()
                                    .referenceBitmark("58737de5ad68a535da6277da62d11eb3ed76ff6dd7fc2adf3c42a4096d9a2518");
    Transaction.list(builder, new Callback1<GetTransactionsResponse>() {
                @Override
                public void onSuccess(GetTransactionsResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Query the transaction history of an account

    let transactionQueryParams = Transaction.newTransactionQueryBuilder()
        .ownedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9")
        .build();
    
    let response = await Transaction.list(transactionQueryParams);
    
    let query = try Transaction.newTransactionQueryParams()
        .owned(by: "eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9", transient: true)
    let (txs, _) = try Transaction.list(params: query)
    
    params := tx.NewQueryParamsBuilder().
        OwnedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9", true)
    
    TransactionQueryBuilder builder = new TransactionQueryBuilder()
                                    .ownedBy("eZpG6Wi9SQvpDatEP7QGrx6nvzwd6s6R8DgMKgDbDY1R5bjzb9");
    Transaction.list(builder, new Callback1<GetTransactionsResponse>() {
                @Override
                public void onSuccess(GetTransactionsResponse res) {
    
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
            });
    

    Account migration

    Bitmark allows users migrate their own account from previous twenty four recovery phrase words to new twelve one.

    The method requires a valid 24 recovery phrase words and return new Account object with 12 recovery phrase words as well as a new list bitmark id.

    let phrase = ["abuse", "tooth", "riot", "whale", "dance", "dawn", "armor", "patch", "tube", "sugar", "edit", "clean",
                    "guilt", "person", "lake", "height", "tilt", "wall", "prosper", "episode", "produce", "spy", "artist", "account"]
    let (account, bitmarkids) = try Migration.migrate(recoverPhrase: phrase, language: .english)
    
    final String[] phrase = new String[] {"abuse", "tooth", "riot", "whale", "dance", "dawn", "armor", "patch", "tube", "sugar", "edit", "clean","guilt", "person", "lake", "height", "tilt", "wall", "prosper", "episode", "produce", "spy", "artist", "account"};
    
    Migration.migrate(phrase, new Callback1<Pair<Account, List<String>>>() {
                    @Override
                    public void onSuccess(Pair<Account, List<String>> data){
                        final Account account = data.first();
                        final List<String> bitmarkids = data.second();
                    }
    
                    @Override
                    public void onError(Throwable throwable) {
                        // Throwable goes here
                    }
                });
    

    Store Seed

    Store key in mobile application is very important to ensure the security, protect user data against attackers. Bitmark is a blockchain platform, everything is decentralized, so we need to keep the user's key more secure to guarantee the ownership. So we provide the utility for storing key securely in mobile application, support Android/iOS platform.

    Android

    // Storing your account
    final Account account = new Account();
    final KeyAuthenticationSpec spec = new KeyAuthenticationSpec.Builder(getApplicationContext()).setKeyAlias(ENCRYPTION_KEY_ALIAS) // The encryption key alias for encrypting your key
                                                     .setAuthenticationRequired(authentication) // true mean the key need to authenticate before using
                                                     .setAuthenticationValidityDuration(100) //  Time for keeping the key is authenticated
                                                     .setAuthenticationTitle("Title")
                                                     .setAuthenticationDescription("Description")
                                                     .build();
    
    account.saveToKeyStore(activity/* the activity that is instance of StatefulActivity*/, alias /* the account alias, avoid it with default account number*/, spec, new Callback0() {
                    @Override
                    public void onSuccess() {
    
                    }
    
                    @Override
                    public void onError(Throwable throwable) {
    
                    }
                });
    
    // Getting your account
    final KeyAuthenticationSpec spec = new KeyAuthenticationSpec.Builder(getApplicationContext())
                                            .setKeyAlias(ENCRYPTION_KEY_ALIAS).build();
    Account.loadFromKeyStore(activity, alias, spec, new Callback1<Account>() {
                    @Override
                    public void onSuccess(Account account) {
    
                    }
    
                    @Override
                    public void onError(Throwable throwable) {
    
                    }
                });
    
    // Deleting your account
    final KeyAuthenticationSpec spec = new KeyAuthenticationSpec.Builder(getApplicationContext())
                                            .setKeyAlias(ENCRYPTION_KEY_ALIAS)
                                            .build();
    account.removeFromKeyStore(activity, spec, new Callback0() {
                    @Override
                    public void onSuccess() {
    
                    }
    
                    @Override
                    public void onError(Throwable throwable) {
    
                    }
                });
    
    

    Bitmark Android SDK supports storing key from Android API 23(M) and above because of the security level.

    Bitmark Android SDK will authenticate user each time they use the key (depend on your KeyAuthenticationSpec), so protects user against attackers. We use Android Key Store system with a lot of security algorithm for protecting user's key.

    NOTE:

    iOS

    // Storing your account
    try account.saveToKeychain(service: "com.bitmarksdk.example",
                                alias: "bitmark-account",
                                requireAuthenticationWithMessage: nil)
    
    // Getting your account
    try Account.loadFromKeychain(service: "com.bitmarksdk.example",
                                    alias: "bitmark-account",
                                    requireAuthenticationWithMessage: nil)
    

    Bitmark Swift SDK uses iOS keychain to store bitmark accounts. To use this feature, on your app, you need to setup the keychain first. Parameters: 1. service: your keychain service name, depends on what you configurated on your entitlement file. 2. alias: the key to save your account into keychain. Default value is your account number.