📬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

Please note that we may add a new field. So make sure that your implementation can support accepting new fields without breaking your implementation. In case we remove the field, we will deprecate it first and let you know beforehand.

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

headervalue

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

  1. Get a signed URL to upload 1 file

  2. Upload 1 file using a signed URL

  3. Check the file readiness

  4. 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.

  • You may need TS-Node and NPX to run it

    • save it as example.ts

    npx ts-node ./script-address/example.ts
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),
    externalUrls: ['https://example.com'],
    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

©2023 Startbahn, Inc.