# Issue & Transfer SRR (NFT)

If you have any concerns, please [contact us](https://startbahn.io/contact).

By using the API/SDK in this document in any way, you agree to [Startrail PORT API/SDK Terms of Service](https://help.port.startrail.io/hc/en-us/articles/12968031055511). Please read these Terms carefully before using the API/SDK.&#x20;

## UML Diagram

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fp83BcoIS04JCYgoJJfJI%2Fimage.png?alt=media&#x26;token=f5382a0b-a5e8-45eb-b1af-1fb0ee6c95c5" alt=""><figcaption></figcaption></figure>

## Endpoints Used

{% hint style="info" %}
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.
{% endhint %}

* Steps 7 - 8: [Request Signed URL for Upload/Download Files](https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/request-signed-url-for-upload-download-files)&#x20;
* Step 9: Upload file to Google Cloud Storage, use Signed URL as the endpoint, [Google Documentation](https://cloud.google.com/storage/docs/access-control/signed-urls)
* Steps 11 - 12: [File Information Metadata](https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/file-information-metadata)
* Steps 13 - 16: [Issue & Transfer SRR](https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/issue-and-transfer)
* Step 17: [Webhook Setup](https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/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

| header           | value                                            |
| ---------------- | ------------------------------------------------ |
| 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:&#x20;

* 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](https://www.npmjs.com/package/ts-node) and [NPX](https://www.npmjs.com/package/npx) to run it

  * save it as `example.ts`

  ```jsx
  npx ts-node ./script-address/example.ts
  ```

```typescript
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/',
  }
}
```
