Authentication
LINE Blockchain Developers API conducts authentication to validate a request. Refer to Introduction to API on how to issue an API key for authentication.
All API requests, except for the endpoint to retrieve the server time, must pass authentication information in the HTTP header. The server validates API requests with the given authentication information as follows. Invalid requests aren't processed.
- An error is returned when the time discrepancy is larger than 5 minutes between the
timestamp
of the request and the current time (Unix Epoch time) of the server. - The same
nonce
can't be reused perservice-api-key
within 11 minutes. An error is returned when thenonce
of the successful request is reused within 11 minutes. - A
signature
is effective only once.
The following authentication information must be included in the header of the request.
Header | Description |
---|---|
timestamp | Time of the request creation displayed in milliseconds since Unix Epoch at UTC. The time gap can’t be greater than 5 minutes against the server time. |
nonce | A random string of 8 characters, composed of uppercase or lowercase alphabets and numbers. nonce can't be reused within 11 minutes after the successful request. |
service-api-key | API key of the service issued from the LINE Blockchain Developers console. It must be duly activated at LINE Blockchain Developers. |
signature | Result of an API request signed with the API secret of the service issued from the LINE Blockchain Developers console. It must match the signature created on the server. Refer to Generating a signature. |
To check your API key, go to Settings under the service of the LINE Blockchain Developers console.
Generating signature
Follow the next steps to generate a signature to be included in the header.
- Create a string by concatenating nonce, timestamp, HTTP method, request path, query string, and request body string in the order listed.
- Sign the string from step 1 with the API secret using HMAC-SHA512.
- Encode the result from step 2 with Base64.
Next is the description of each value used in the signature.
- The nonce value and timestamp are the same as
nonce
andtimestamp
sent in the header. - The HTTP method is a string of the HTTP method of the API request, written in uppercase letters.
- Request path refers to
api-path
of the API endpoint.
For example, when the requested API endpoint ishttps://test-api.blockchain.line.me/v1/wallets/link000000000000000000000000000000000000000?page=2&msgType=MsgSend
, its request path is/v1/wallets/link000000000000000000000000000000000000000
. - Query string refers to the query parameter of the API endpoint.
For example, when the requested API endpoint is
https://test-api.blockchain.line.me/v1/wallets/link000000000000000000000000000000000000000?page=2&msgType=MsgSend
, the query string ispage=2&msg=MsgSend
. - The request body string refers to the key and value included in the HTTP request body, connected in the form of 'key=value&', just like a query string. All keys should be sorted in ascending order and connected in the corresponding sequence.
- For the array in the request body, the keys of the parent and its leaf child are concatenated with a period and used as a full key. The values of each array are concatenated with a comma and used as a full value. Refer to Example 4.
- For the optional keys in the array, you must use an empty string for the unassigned values. Refer to Example 4.
JavaScript Sample
The following code shows how to generate a signature with JavaScript.
This is based on ES6. Use Babel to support older versions.
- signature-generator.js
- request-body-flattener.js
- .babelrc
import CryptoJs from "crypto-js";
import _ from "lodash";
import RequestBodyFlattener from "./request-body-flattener";
export default class SignatureGenerator {
/*
* path has to include only path such as /v1/item-tokens/{contractId}/non-fungibles, without any query-string
* parameters is query-parameters
* body is request body of POST, PUT method
*/
static signature(apiSecret, method, path, timestamp, nonce, parameters = {}, body = {}) {
let obj = _.assignIn(parameters, body);
function createSignTarget() {
let signTarget = `${nonce}${timestamp}${method}${path}`;
if (obj && _.size(obj) > 0) {
if (signTarget.indexOf('?') < 0) {
signTarget += '?'
} else {
signTarget += '&'
}
}
return signTarget;
}
let signTarget = createSignTarget();
if (obj && _.size(obj) > 0) {
signTarget += RequestBodyFlattener.flatten(obj);
}
let hash = CryptoJs.HmacSHA512(signTarget, apiSecret);
return CryptoJs.enc.Base64.stringify(hash);
}
}
import _ from "lodash";
const EMPTY = "";
export default class RequestBodyFlattener {
static flatten(requestBody = {}) {
const objBody = _.cloneDeep(requestBody)
const flatPair = {}
Object.keys(objBody).forEach(key => {
const value = objBody[key]
if(Array.isArray(value)) {
let allSubKeys = []
value.forEach(elem => {
allSubKeys = _.union(allSubKeys, Object.keys(elem))
})
value.forEach(elem => {
allSubKeys.forEach(subKey => {
const flatKey = `${key}.${subKey}`
const flatRawValue = elem[subKey] ? elem[subKey] : EMPTY
const prevFlatValue = flatPair[flatKey]
flatPair[flatKey] =
_.isUndefined(prevFlatValue) ? flatRawValue : `${prevFlatValue},${flatRawValue}`
})
})
} else {
flatPair[key] = objBody[key]
}
})
return Object.keys(flatPair).sort().map(key => `${key}=${flatPair[key]}`).join('&');
}
}
{
"presets": ["@babel/preset-env"]
}
Python Sample
The following code shows how to generate a signature with Python.
- signature_generator.py
- request_flattener.py
import hmac
import hashlib
import base64
import logging
import sys
from sdk.request_flattener import RequestBodyFlattener
class SignatureGenerator:
def __createSignTarget(self, method, path, timestamp, nonce, parameters: dict = {}):
signTarget = f'{nonce}{str(timestamp)}{method}{path}'
if(len(parameters) > 0):
signTarget = signTarget + "?"
return signTarget
def generate(self, secret: str, method: str, path: str, timestamp: int, nonce: str, query_params: dict = {}, body: dict = {}):
body_flattener = RequestBodyFlattener()
all_parameters = {}
all_parameters.update(query_params)
all_parameters.update(body)
self.__logger.debug("query_params: " + str(query_params))
signTarget = self.__createSignTarget(method.upper(), path, timestamp, nonce, all_parameters)
if (len(query_params) > 0):
signTarget += '&'.join('%s=%s' % (key, value) for (key, value) in query_params.items())
if (len(body) > 0):
if (len(query_params) > 0):
signTarget += "&" + body_flattener.flatten(body)
else:
signTarget += body_flattener.flatten(body)
raw_hmac = hmac.new(bytes(secret, 'utf-8'), bytes(signTarget, 'utf-8'), hashlib.sha512)
result = base64.b64encode(raw_hmac.digest()).decode('utf-8')
return result
class RequestBodyFlattener:
def __flatten_key_value(self, key, value):
if (isinstance(value, str)):
return f"{key}={value}"
if (isinstance(value, list)):
l_key_value = {}
for index, ele in enumerate(value):
for lkey in list(ele.keys() | l_key_value.keys()):
if lkey in ele.keys():
lvalue = ele[lkey]
else:
lvalue = ""
if (lkey in l_key_value.keys()):
l_key_value[lkey] = f"{l_key_value[lkey]},{lvalue}"
else:
l_key_value[lkey] = f"{',' * index}{lvalue}"
return "&".join("%s=%s" % (f"{key}.{lkey}", lvalue) for (lkey, lvalue) in sorted(l_key_value.items()))
def flatten(self, body: dict = {}):
sorted_body = sorted(body.items())
return "&".join(self.__flatten_key_value(key, value) for (key, value) in sorted_body)
Kotlin Sample
The following code shows how to generate a signature with Kotlin.
- SignatureGenerator.kt
- QueryParameterFlattener.kt
- RequestBodyFlattener.kt
import org.apache.commons.codec.binary.Base64
import java.util.TreeMap
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
interface SignatureGenerator {
fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
flatQueryParam: String,
body: Map<String, Any?> = emptyMap(),
): String
fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
queryParam: Map<String, List<String?>> = emptyMap(),
body: Map<String, Any?> = emptyMap(),
): String
}
class DefaultSignatureGenerator(
private val queryParameterFlattener: QueryParameterFlattener,
private val requestBodyFlattener: RequestBodyFlattener,
) : SignatureGenerator {
override fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
queryParam: Map<String, List<String?>>,
body: Map<String, Any?>,
): String {
val flattenQueryParam = queryParameterFlattener.flatten(queryParam)
return generate(
serviceApiSecret,
httpMethod,
path,
timestamp,
nonce,
flattenQueryParam,
body,
)
}
override fun generate(
serviceApiSecret: String,
httpMethod: String,
path: String,
timestamp: Long,
nonce: String,
flatQueryParam: String,
body: Map<String, Any?>,
): String {
val data = signatureTarget(body, nonce, timestamp, httpMethod, path, flatQueryParam)
val rawHmac = rawSignature(serviceApiSecret, data)
return Base64.encodeBase64String(rawHmac)
}
private fun rawSignature(serviceApiSecret: String, data: String): ByteArray? {
val signingKey = SecretKeySpec(serviceApiSecret.toByteArray(), HMAC_512_SECRET_ALGORITHM)
val mac = Mac.getInstance(HMAC_512_SECRET_ALGORITHM)
mac.init(signingKey)
return mac.doFinal(data.toByteArray())
}
private fun signatureTarget(body: Map<String, Any?>, nonce: String, timestamp: Long, httpMethod: String, path: String, flatQueryParam: String): String {
val bodyTreeMap = sortBody(body)
val flattenBody = requestBodyFlattener.flatten(bodyTreeMap)
val stringBuilder = StringBuilder()
stringBuilder.append("$nonce$timestamp$httpMethod$path")
if (flatQueryParam.isNotBlank()) {
if ("?" in flatQueryParam) {
stringBuilder.append(flatQueryParam)
} else {
stringBuilder.append("?").append(flatQueryParam)
}
}
if (body.isNotEmpty()) {
if (!stringBuilder.contains('?')) {
stringBuilder.append("?").append(flattenBody)
} else {
stringBuilder.append("&").append(flattenBody)
}
}
return stringBuilder.toString()
}
private fun sortBody(body: Map<String, Any?>): TreeMap<String, Any?> {
val bodyTreeMap = TreeMap<String, Any?>()
bodyTreeMap.putAll(body)
return bodyTreeMap
}
companion object {
private const val HMAC_512_SECRET_ALGORITHM = "HmacSHA512"
}
}
import java.util.TreeMap
interface QueryParameterOrderer {
fun sort(queryParams: Map<String, List<String?>>): Map<String, List<String?>>
fun order(queryParams: Map<String, List<String?>>): Map<String, List<String?>> {
return sort(queryParams)
}
}
interface QueryParameterFlattener {
fun flatten(queryParams: Map<String, List<String?>>): String
}
class DefaultOrderedQueryParameterFlattener: QueryParameterFlattener, QueryParameterOrderer {
override fun flatten(queryParams: Map<String, List<String?>>): String {
val orderedMap = order(queryParams)
return if (orderedMap.isEmpty()) {
""
} else {
orderedMap.filterValues { it.isNotEmpty() }.map { (k, v) ->
"$k=${v.joinToString(",")}"
}.joinToString("&")
}
}
override fun sort(queryParams: Map<String, List<String?>>): Map<String, List<String?>> {
return TreeMap<String, List<String?>>(queryParams)
}
}
import org.apache.commons.lang3.StringUtils
import java.util.TreeMap
interface RequestBodyFlattener {
fun flatten(body: Map<String, Any?>): String
}
class DefaultRequestBodyFlattener: RequestBodyFlattener {
override fun flatten(body: Map<String, Any?>): String {
val bodyTreeMap = TreeMap<String, Any?>()
bodyTreeMap.putAll(body)
@Suppress("UNCHECKED_CAST")
return if (bodyTreeMap.isEmpty()) {
""
} else {
bodyTreeMap.filterValues { it != null }.map { (k, v) ->
when (v) {
is String -> "$k=$v"
is List<*> -> {
val listTreeMap = TreeMap<String, String?>()
v as List<Map<String, String>>
v.forEachIndexed { index, map ->
map.keys.union(listTreeMap.keys).forEach { key ->
val value = map[key] ?: StringUtils.EMPTY
if (listTreeMap[key] == null) {
listTreeMap[key] = "${",".repeat(index)}$value"
} else {
listTreeMap[key] = "${listTreeMap[key]},$value"
}
}
}
listTreeMap.map { (lk, kv) ->
"$k.$lk=$kv"
}.joinToString("&")
}
else -> throw IllegalArgumentException()
}
}.joinToString("&")
}
}
}
Go Sample
The following code shows how to generate a signature with Go-lang.
- signature_generator.go
- request_body_flattener.go
import (
"fmt"
"strings"
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
)
func __createSignTarget(method string, path string, timestamp int, nonce string, parameters map[string]interface{}) string {
s := fmt.Sprint(timestamp)
signTarget := fmt.Sprintf("%s%s%s%s", nonce, s, method, path)
if len(parameters) > 0 {
signTarget = fmt.Sprintf("%s?", signTarget)
}
return signTarget;
}
func Generate(secret string,
method string,
path string,
timestamp int,
nonce string,
query_params map[string]interface{},
body map[string]interface{}) string {
//
// Generate signature with given arguments.
//
// Args:
// -secret- api-secret
// -method- http method
// -path- api path
// -timestamp- Unix timestamp value
// -nonce- random string with 8 length
// -query_params- query parameters
// -body- request body
//
// Returns:
// -signature- generated signature
//
//
all_parameters := make(map[string]interface{})
for k, v := range query_params {
all_parameters[k] = v
}
for k, v := range body {
all_parameters[k] = v
}
signTarget := __createSignTarget(strings.ToUpper(method), path, timestamp, nonce, all_parameters);
if len(all_parameters) > 0 {
signTarget = fmt.Sprintf("%s%s", signTarget, Flatten(all_parameters))
}
raw_hmac := hmac.New(sha512.New, []byte(secret))
raw_hmac.Write([]byte(signTarget))
result := base64.StdEncoding.EncodeToString(raw_hmac.Sum(nil))
return result
}
import (
"fmt"
"sort"
"strings"
)
func _createFlatPair(flatPair map[string]string, body map[string]interface{}) {
for key, value := range(body) {
is_primitive := false;
expr := "";
switch value.(type) {
case []interface{}:
allSubKeys := make(map[string]bool, 0)
for _, subv:= range value.([]interface{}) {
submap := subv.(map[string]interface{})
for k, _ := range(submap) {
allSubKeys[k] = true;
}
}
for _, subv:= range value.([]interface{}) {
submap := subv.(map[string]interface{})
for subKey, _ := range(allSubKeys) {
flatKey := fmt.Sprintf("%s.%s", key, subKey)
flatRawValue := "";
if x, found := submap[subKey]; found {
flatRawValue = x.(string)
}
if prevFlatValue, found := flatPair[flatKey]; found {
flatPair[flatKey] = fmt.Sprintf("%s,%s", prevFlatValue, flatRawValue)
} else {
flatPair[flatKey] = flatRawValue
}
}
}
// handle primitive types
default:
expr = fmt.Sprint(value)
is_primitive = true;
}
if is_primitive {
flatPair[key] = expr;
}
}
}
func Flatten(body map[string]interface{}) string {
flatPair := make(map[string]string) // we're going to convert objBody to flatPair
_createFlatPair(flatPair, body)
keys := make([]string, 0, len(flatPair))
flattenBody := make([]string, 0, len(flatPair))
for k := range flatPair {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
flattenBody = append(flattenBody, fmt.Sprintf("%s=%s", k, flatPair[k]))
}
ret := strings.Join(flattenBody, "&")
return ret;
}
PHP Sample
The following code shows how to generate a signature with PHP.
- signature_generator.php
- request_body_flattener.php
<?php
require_once("request_body_flattener.php");
class SignatureGenerator {
// This is to generate signature with flatten request.
function __createSignTarget($method, $path, $timestamp, $nonce, $parameters) {
$s = strval($timestamp);
$signTarget = "{$nonce}{$s}{$method}{$path}";
if (count($parameters) > 0)
$signTarget = $signTarget."?";
return $signTarget;
}
public function generate($secret, $method, $path, $timestamp, $nonce, $query_params = array(), $body = array()) {
//
// Generate signature with given arguments.
//
// Args:
// -secret- api-secret
// -method- http method
// -path- api path
// -timestamp- Unix timestamp value
// -nonce- random string with 8 length
// -query_params- query parameters
// -body- request body
//
// Returns:
// -signature- generated signature
//
//
$body_flattener = new RequestBodyFlattener();
$all_parameters = array_replace($query_params, $body);
$signTarget = $this->__createSignTarget(strtoupper($method), $path, $timestamp, $nonce, $all_parameters);
if (count($all_parameters) > 0)
$signTarget = $signTarget.$body_flattener->flatten($all_parameters);
$raw_hmac = hash_hmac("sha512", $signTarget, $secret, true);
$result = base64_encode($raw_hmac);
return $result;
}
}
?>
<?php
class RequestBodyFlattener {
public function flatten($body) {
$objBody = array_merge(array(), $body);
$flatPair = []; // we're going to convert objBody to flatPair
foreach ($objBody as $key => $value) {
if (is_int($value))
$value = strval($value);
if (is_array($value)) {
$allSubKeys = [];
foreach ($value as $elem) {
$allSubKeys += array_keys($elem);
}
foreach ($value as $elem) {
foreach ($allSubKeys as $subKey) {
$flatKey = "{$key}.{$subKey}";
$flatRawValue = $elem[$subKey] ? $elem[$subKey] : "";
$prevFlatValue = $flatPair[$flatKey];
$flatPair[$flatKey] =
is_null($prevFlatValue) ? $flatRawValue : "{$prevFlatValue},{$flatRawValue}";
}
}
} else {
$flatPair[$key] = $value;
}
}
ksort($flatPair);
$flattenBody = [];
foreach ($flatPair as $key => $value) {
array_push($flattenBody, "{$key}={$value}");
}
return join("&", $flattenBody);
}
}
?>
Examples
Let's take a look at some examples to understand how to generate a signature and send an API request. We'll use the following information for all examples.
- API Key: 136db0ad-0fe1-456f-96a4-329be3f93036
- API Secret: 9256bf8a-2b86-42fe-b3e0-d3079d0141fe
- Timestamp: 1581850266351
- Nonce: Bp0IqgXE
Example 1. When there is a request path only
The first example is an API request with the request path only.
- HTTP method: GET
- Request path: /v1/wallets
Generate the string required for the signature as follows.
Bp0IqgXE1581850266351GET/v1/wallets
Create an HMAC SHA512 signature using echo
and OpenSSL, and encode with Base64.
echo -n "Bp0IqgXE1581850266351GET/v1/wallets" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64
> 2LtyRNI16y/5/RdoTB65sfLkO0OSJ4pCuz2+ar0npkRbk1/dqq1fbt1FZo7fueQl1umKWWlBGu/53KD2cptcCA==
Send the generated signature using curl
via the header of the API request.
curl -i GET 'https://test-api.blockchain.line.me/v1/wallets' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: 2LtyRNI16y/5/RdoTB65sfLkO0OSJ4pCuz2+ar0npkRbk1/dqq1fbt1FZo7fueQl1umKWWlBGu/53KD2cptcCA=='
Example 2. When there are request path and query parameter
The second example is an API request with the request path and query parameter.
- HTTP method: GET
- Request URI: /v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend
- Request path: /v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions
- Query string: page=2&msgType=coin/MsgSend
Generate the string required for the signature as follows. Note that '?' is used between the request path and the query string. Also, you must keep the original sequence when entering query parameters.
Bp0IqgXE1581850266351GET/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend
Create an HMAC SHA512 signature using echo
and OpenSSL, and encode with Base64.
echo -n "Bp0IqgXE1581850266351GET/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64
> fasfnqKVVClFam+Dov+YN+rUfOo/PMZfgKx8E36YBtPh7gB2C+YJv4Hxl0Ey3g8lGD0ErEGnD0gqAt85iEhklQ==
Send the generated signature using curl
via the header of the API request.
curl -i GET 'https://test-api.blockchain.line.me/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: fasfnqKVVClFam+Dov+YN+rUfOo/PMZfgKx8E36YBtPh7gB2C+YJv4Hxl0Ey3g8lGD0ErEGnD0gqAt85iEhklQ=='
Example 3. When there are request path and request body (excluding an array)
The third example is an API request with the request path and request body.
- HTTP method: PUT
- Request path: /v1/item-tokens/61e14383/non-fungibles/10000001/00000001
- Request body string:
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"name": "NewName"
}
String required for the signature can be generated as follows.
Bp0IqgXE1581850266351PUT/v1/item-tokens/61e14383/non-fungibles/10000001/00000001?name=NewName&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=
As shown above, note that '?' is used between the request path and the request body string, even without the query string. In addition, the request body should be added after the keys are sorted in ascending order.
You can use a sorting function provided by the programming language you're using.
Create an HMAC SHA512 signature using echo
and OpenSSL, and encode with Base64.
echo -n "Bp0IqgXE1581850266351PUT/v1/item-tokens/61e14383/non-fungibles/10000001/00000001?name=NewName&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64
> 4L5BU0Ml/ejhzTg6Du12BDdElv8zoE7XD/iyOaZ2BHJIJG0SUOuCZWXu0YaF4i4C2CFJhjZoJFsje4CJn/wyyw==
Send the generated signature using curl
via the header of the API request.
curl -i -X PUT 'https://test-api.blockchain.line.me/v1/item-tokens/61e14383/non-fungibles/10000001/00000001' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: 4L5BU0Ml/ejhzTg6Du12BDdElv8zoE7XD/iyOaZ2BHJIJG0SUOuCZWXu0YaF4i4C2CFJhjZoJFsje4CJn/wyyw==' \
--header 'Content-Type: application/json' \
--data-raw '{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"name": "NewName"
}'
Example 4. When there are request path and request body (including an array)
The last example is an API request with the request path and request body including an array.
- HTTP method: POST
- Request path: /v1/item-tokens/61e14383/non-fungibles/multi-mint
- Request body:
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": "New nft 2 meta information"
}
]
}
String required for the signature can be generated as follows.
Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.meta=,New nft 2 meta information&mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp
As shown above, note that '?' is included between the request path and the request body string, even without the query string. In addition, the request body should be added after keys are sorted in the ascending order.
You can use a sorting function provided by the programming language you're using.
In the example above, the first element in mintList
doesn't have meta
as it's optional. Then, use an empty string for meta
of the first element like "mintList.meta=,New nft 2 meta information." If the second element doesn't have meta
either, exclude the "mintList.meta" from the string.
Create a HMAC SHA512 signature, using echo
and OpenSSL, and encode with Base64.
echo -n "Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.meta=,New nft 2 meta information&mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64
> vhr5c3y2PAP5rmt+4YN1ojbMnT9IkYnIIB1yvWYM9OdECB2Y11fGTLDLRybB3lLKv0kvJQMAelSkQYBKdhSXbg==
Send the generated signature using curl
via the header of the API request.
curl -i -X POST 'https://test-api.blockchain.line.me/v1/item-tokens/61e14383/non-fungibles/multi-mint' \
--header 'service-api-key: 136db0ad-0fe1-456f-96a4-329be3f93036' \
--header 'nonce: Bp0IqgXE' \
--header 'timestamp: 1581850266351' \
--header 'signature: vhr5c3y2PAP5rmt+4YN1ojbMnT9IkYnIIB1yvWYM9OdECB2Y11fGTLDLRybB3lLKv0kvJQMAelSkQYBKdhSXbg==' \
--header 'Content-Type: application/json' \
--data-raw '{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": "New nft 2 meta information"
}
]
}'
What if the mintList
in the request body doesn't use meta
as shown below?
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2"
}
]
}
Exclude mintList.meta
key from the string to be signed as shown below.
Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp
Let's take a look at meta
, an optional field in mintList
, is given null.
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": null
}
]
}
In this case, use the following string excluding null for signature.
Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp