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.
Copy 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/',
}
}