📬Issue & Transfer SRR (NFT)
If you have any concerns, please contact us.
By using the API/SDK in this document in any way, you agree to Startrail PORT API/SDK Terms of Service. Please read these Terms carefully before using the API/SDK.
UML Diagram

Endpoints Used
Steps 7 - 8: Request Signed URL for Upload/Download Files
Step 9: Upload file to Google Cloud Storage, use Signed URL as the endpoint, Google Documentation
Steps 11 - 12: File Information Metadata
Steps 13 - 16: Issue & Transfer SRR
Step 17: Webhook Setup
Required Permissions
You need to have a Licensed User
See
issuer-address
in the following Required Headers section.
You need to have an API Key
See
commerce-api-key
in the following Required Headers section.
Required Headers
issuer-address
The Ethereum address of your LUW
commerce-api-key
The API key you generated in the web application
Content-Type
application/json
accept
application/json
Complete Code Example
In this example, we will do
Get a signed URL to upload 1 file
Upload 1 file using a signed URL
Check the file readiness
To simplify the example, we use the same uploaded file's final URL for
contract terms
thumbnail
attachment file
Note:
This example does not cover the webhook on the client’s side that Startbahn API will call when issuance or mining failure happens.
import fs from 'fs'
import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'
main('issuerAddress', 'artistAddress', 'apiKey', 'receiverAddress') // Use your staging credential to test
async function main(
luwIssuer: string,
luwArtist: string,
apiKey: string,
toAddress: string | undefined
) {
console.log('Starting the issue API example')
const headersCommerceApi = {
'Content-Type': 'application/json',
accept: 'application/json',
'commerce-api-key': apiKey,
'issuer-address': luwIssuer,
}
/////////////////////////
// GENERATE SIGNED URL //
// STEP 7 - 8 //
/////////////////////////
console.log('Starting to generate signed URL')
const signedUrlEndpoint =
'https://api-stg.startrail.startbahn.jp/port/api/v1/commerce/signedUrls'
const fileName = `test-${uuidv4()}.txt`
const fileWs = fs.createWriteStream(fileName)
fileWs.write('test')
fileWs.end()
const requestBodySignedUrl = {
payload: [
{
filename: fileName,
category: 'artwork',
},
],
action: 'write',
}
const responseSignedUrl = await fetch(signedUrlEndpoint, {
method: 'POST',
body: JSON.stringify(requestBodySignedUrl),
headers: headersCommerceApi,
})
if (!responseSignedUrl.ok) {
console.log('handle the error')
}
const jsonSignedUrlResponse = await responseSignedUrl.json()
const contentType = jsonSignedUrlResponse.results[0].contentType
// Upload the file to the Signed URL
// example using cURL
// `curl -X PUT -H 'Content-Type: text/plain' --upload-file my-file.txt '${res.url}
const signedUrl = jsonSignedUrlResponse.results[0].url
// save res.finalUrl and use it for Issue endpoint
const finalUrl = jsonSignedUrlResponse.results[0].finalUrl
/////////////////////////////
// UPLOAD USING SIGNED URL //
// STEP 9 //
/////////////////////////////
console.log('Starting to upload the file')
const fileRs = fs.createReadStream(fileName)
const responseUpload = await fetch(signedUrl, {
method: 'PUT',
headers: {
'Content-Type': contentType,
},
body: fileRs,
})
if (!responseUpload.ok) {
console.log('handle the error')
}
///////////////////////////
// CHECK FILE READINESS //
// STEP 11 - 12 //
///////////////////////////
console.log('Starting to check the file readiness')
await sleep(360000) // wait to make sure the hash is calculated
const fileInfoEndpoint =
'https://api-stg.startrail.startbahn.jp/port/api/v1/commerce/fileMetadata'
const requestBodyFileInfo = {
payload: [
{
filename: fileName,
category: 'artwork',
},
],
}
const responseFileInfo = await fetch(fileInfoEndpoint, {
method: 'POST',
body: JSON.stringify(requestBodyFileInfo),
headers: headersCommerceApi,
})
if (!responseFileInfo.ok) {
console.log('handle the error')
}
const jsonFileInfoResponse = await responseFileInfo.json()
const calculatedHash = jsonFileInfoResponse.results[0].hash
if (!calculatedHash) {
console.log('There is error in hash calculation, contact Startbahn')
}
//////////////////
// ISSUE SRR //
// STEP 13 - 16 //
//////////////////
console.log('Starting to issuen an SRR')
const issueEndpoint =
'https://api-stg.startrail.startbahn.jp/port/api/v1/commerce/srrs'
const singlePayload: any = {
externalId: uuidv4(),
artistAddress: luwArtist,
isPrimaryIssuer: true,
lockExternalTransfer: false,
// If the uploaded file is thumbnail or contract terms, it should be put on the metadata
metadata: randomizeMetadata(finalUrl, finalUrl),
attachmentFiles: [
{
name: requestBodyFileInfo.payload[0].filename,
// If the uploaded file is an attachment file, put it in this field
url: finalUrl,
category: requestBodyFileInfo.payload[0].category,
},
],
}
if (toAddress) {
singlePayload.to = toAddress
}
const requestBodyissue = {
payload: [singlePayload],
}
const responseIssue = await fetch(issueEndpoint, {
method: 'POST',
body: JSON.stringify(requestBodyissue),
headers: headersCommerceApi,
})
if (!responseIssue.ok) {
console.log('handle the error')
}
const jsonIssueResponse = await responseIssue.json()
console.log(jsonIssueResponse)
}
async function sleep(milliseconds: number): Promise<NodeJS.Timeout> {
return new Promise((resolve) => setTimeout(resolve, milliseconds))
}
function randomizeMetadata(
thumbnailURL: string,
contractTermsFileURL: string
) {
return {
$schema:
'https://api.startrail.io/api/v1/schema/registry-record-metadata.v2.1.schema.json',
$schemaIntegrity:
'sha256-15f8e99eb9d4292287282942db2f2de9bbcc4761c555c6f7da23feec010c1221',
title: {
en: 'A title-' + uuidv4(),
ja: 'タイトル-' + uuidv4(),
zh: '一个标题-' + uuidv4(),
},
size: {
width: 200.0,
height: 400.0,
depth: 12.4,
unit: 'pixel',
flexibleDescription: {
en: 'flexibleDescription comes here',
ja: '自由だーーー',
},
},
medium: {
en: 'Oil on canvas',
ja: 'キャンバスに油彩',
zh: '布面油画',
},
edition: {
uniqueness: 'unique work',
proofType: 'ED',
number: 1,
totalNumber: 3,
note: {
en: 'some extra notes in 1 or more languages',
},
},
contractTerms: {
royaltyRate: 15.7,
fileURL: contractTermsFileURL,
},
note: {
en: 'note',
zh: '注意',
},
thumbnailURL,
yearOfCreation: {
en: 'around 2010-2020',
ja: '2010年から2020年頃',
},
isDigital: true,
name: 'some nft name',
description: 'some nft description',
image:
'https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png',
external_url: 'https://startrail.io/',
}
}
Last updated
Was this helpful?