17 KiB
Welcome to Tangem
The Tangem card is a self-custodial hardware wallet for blockchain assets. The main functions of Tangem cards are to securely create and store a private key from a blockchain wallet and sign blockchain transactions. The Tangem card does not allow users to import/export, backup/restore private keys, thereby guaranteeing that the wallet is unique and unclonable.
Getting Started
Requirements
Android with minimal SDK version of 21 and a device with NFC support
Installation
- Add Tangem library to the project:
Add to a project build.gradle file:
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
And add Tangem library to the dependencies (in an app or module build.gradle file):
dependencies {
implementation "com.github.tangem.tangem-sdk-android:tangem-core:$latestVersion"
implementation "com.github.tangem.tangem-sdk-android:tangem-sdk:$latestVersion"
}
tangem-core is a JVM library (without Android dependencies) that provides core functionality of interacting with Tangem cards.
tangem-sdk is an Android library that implements NFC interaction between Android devices and Tangem cards and graphical interface for this interaction.
- Save the file (you can name it anything you wish) with the following tech-list filters in the
<project-root>/res/xml
<resources>
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
</resources>
- Add to
AndroidManifest.xml:
<intent-filter>
<action android:name=“android.nfc.action.TECH_DISCOVERED” />
</intent-filter>
<meta-data
android:name=“android.nfc.action.TECH_DISCOVERED”
android:resource=“@xml/tech_filter” />
Usage
Tangem SDK is a self-sufficient solution that implements a card abstraction model, methods of interaction with the card and interactions with the user via UI.
Initialization
To get started, you need to create an instance of the TangemSdk class. It provides the simple way of interacting with the card.
val tangemSdk: TangemSdk = TangemSdk.init(activity)
TangemSdk requires a reference to activity in order to use Android API for interacting with NFC and to render views.
Default implementation of TangemSdk allows you to start using SDK in your application without any additional setup.
(You can read about additional options in Customization).
Card interaction
Scan card
To obtain data from the card use scanCard() method. This method launches an NFC session, and once it’s connected with the card, it obtains the card data. If the card contains a wallet (private and public key pair), it proves that the wallet owns a private key that corresponds to a public one.
Example:
tangemSdk.scanCard { result ->
when (result) {
is CompletionResult.Success -> {
// Handle returned card data
val card = result.data
cardId = card.cardId
// Switch to UI thread to show results in UI
runOnUiThread {
tv_card_cid?.text = cardId
}
}
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
}
}
Communication with the card is an asynchronous operation. In order to get a result for the method, you need to implement the callback function. In order to render the callback results on UI, you need to switch to the main thread.
CompletionResult<T> – this is the sealed class for the results of CardSessionRunnable.
Success<T>(val data: T) is triggered after successful operation and contains a CommandResponse.
Failure<T>(val error: TangemSdkError) is triggered on error.
Sign
This method allows you to sign one or multiple hashes. Simultaneous signing of array of hashes in a single SIGN command is required to support Bitcoin-type multi-input blockchains (UTXO). The SIGN command will return a corresponding array of signatures.
tangemSdk.sign(
hashes = arrayOf(hash1, hash2),
cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> {
val signResponse = result.data
}
}
}
Issuer Data
Read Issuer Data
An example of usage (description is available at documentation for TangemSdk.readIssuerData method and corresponding command class):
tangemSdk.readIssuerData(cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> {
val issuerData = result.data.issuerData
val issuerDataSignature = result.data.issuerDataSignature
}
}
}
Read Issuer Extra Data
An example of usage (description is available at documentation for TangemSdk.readIssuerExtraData method and corresponding command class):
tangemSdk.readIssuerExtraData(cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> {
val issuerData = result.data.issuerData
val issuerDataSignature = result.data.issuerDataSignature
}
}
}
Write Issuer Data
An example of usage (description is available at documentation for TangemSdk.writeIssuerData method and corresponding command class):
tangemSdk.writeIssuerData(cardId, issuerData, issuerDataSignature, issuerDataCounter) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> {
val writeIssuerDataResult = result.data
}
}
}
Write Issuer Extra Data
An example of usage (description is available at documentation for TangemSdk.writeIssuerExtraData method and corresponding command class):
tangemSdk.writeIssuerExtraData(cardId,
issuerData, startingSignature, finalizingSignature, issuerDataCounter
) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val writeIssuerDataResult = result.data
}
}
}
User Data
Write User Data
An example of usage (description is available at documentation for TangemSdk.writeUserData method and corresponding command class):
tangemSdk.writeUserData(
cardId, userData, userProtectedData, userCounter, userProtectedCounter
) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val writeUserDataResult = result.data
}
}
}
Read User Data
An example of usage (description is available at documentation for TangemSdk.readUserData method and corresponding command class):
tangemSdk.readUserData(cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val readUserDataResult = result.data
}
}
}
Wallet
Create Wallet
An example of usage (description is available at documentation for TangemSdk.createWallet method and corresponding command class):
tangemSdk.createWallet(cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val createWalletResult = result.data
}
}
}
Purge Wallet
An example of usage (description is available at documentation for TangemSdk.purgeWallet method and corresponding command class):
tangemSdk.purgeWallet(cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val purgeWalletResult = result.data
}
}
}
Personalization
Depersonalize
An example of usage (description is available at documentation for TangemSdk.depersonalize method and corresponding command class):
tangemSdk.depersonalize(cardId) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val depersonalizeResult = result.data
}
}
}
Personalize
An example of usage (description is available at documentation for TangemSdk.personalize method and corresponding command class):
tangemSdk.personalize(config, issuer, manufacturer, acquirer) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> runOnUiThread {
val card = result.data
}
}
}
Customization
UI
If the interaction with user is required, the SDK performs the entire cycle of this interaction. In order to change the appearance or behavior of the user UI, you can provide you own implementation of the SessionViewDelegate interface. After this, initialize the TangemSdk class with your delegate class.
val tangemSdk = TangemSdk.customInit(this, MySessionViewDelegate())
Reader
TangemSdk uses Android NFC capabilities to communicate with TangemCards. If there is a need to use other means of communication or other platforms, it is possible to implement this functionality by implementing CardReader interface and using it instead of the default NfcReader.
Custom Tasks
TangemSdk specific methods run particular commands and tasks. If you want to trigger card commands in a different order, or implement additional
business logic, you have several options.
One option is to start a session, get an instance of CardSession, and use it to perform commands.
To do this, you need to call tangemSdk.startSession() method and get a CardSession instance in a callback.
For example, if you want to read the card and immediately sign a transaction with it, you can achieve it this way.
tangemSdk.startSession { session, error ->
if (error == null) {
session.startWithRunnable(
SignCommand(createSampleHashes())) {result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.UserCancelledError) {
// Handle case when user cancelled manually
}
// Handle other errors
}
is CompletionResult.Success -> {
val signResponse = result.data
}
}
}
}
}
Another option is to put all logic in a in CardSessionRunnable class and launch it using tangemSdk.startSessionWithRunnable() method.
In Tangem SDK we use this approach for our ScanTask.
For example, if you want to read the card and immediately sign a transaction with it, with subclassing
CardSessionRunnableyou can achieve it this way.
class OneTapSignTask(private val hashesToSign: Array<ByteArray>) : CardSessionRunnable<Card> {
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val card = session.environment.card
if (card == null) {
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
} else if (card.cardData?.productMask?.contains(Product.Tag) != false) {
callback(CompletionResult.Success(card))
} else if (card.status != CardStatus.Loaded) {
callback(CompletionResult.Success(card))
} else if (card.curve == null || card.walletPublicKey == null) {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
} else {
val signCommand = SignCommand(hashesToSign)
signCommand.run(session) { result ->
when (result) {
is CompletionResult.Success -> callback(CompletionResult.Success(result.data))
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}
}