@startbahn/startrail-sdk-js
Front-end (browser) JavaScript/TypeScript SDK for the Startrail platform.
It authenticates a user's Ethereum wallet — Startrail (social / email login powered by Torus) or MetaMask — and signs Startrail meta-transactions on their behalf: minting Startrail Records (SRRs), managing collections, transferring ownership with provenance, and reading royalties. Ships with full TypeScript types.
- One entry point: the
Startrailclass. - Lifecycle:
new Startrail(config)→await sdk.login()→ call an action. - Browser only — it opens wallet popups and expects a DOM (not for Node runtimes).
For AI coding tools: a machine-readable API reference lives in
llms.txt, and a short agent how-to inAGENTS.md.
Install
pnpm add @startbahn/startrail-sdk-js
# or
npm i @startbahn/startrail-sdk-js
# or
yarn add @startbahn/startrail-sdk-js
Or via a script tag (UMD build exposes a global Startrail):
<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@startbahn/startrail-sdk-js"></script>
<!-- unpkg -->
<script src="https://unpkg.com/@startbahn/startrail-sdk-js"></script>
Quick start
import { Startrail } from '@startbahn/startrail-sdk-js'
// No config needed to target the Startrail production endpoint with the default Torus wallet.
const sdk = new Startrail({ env: 'staging', wallet: 'startrail', lang: 'en' })
// 1) Authenticate (opens the wallet login flow)
const eoas = await sdk.login()
if (!eoas) throw new Error('login was cancelled by the user')
// 2) Read who logged in
const user = await sdk.getUserInfo()
// 3) Act — e.g. mint a Startrail Record
const srr = await sdk.createSRR({
isPrimaryIssuer: true,
metadata: { name: 'Artwork #1' },
artistAddress: '0xArtist…',
lockExternalTransfer: false,
startrailLUWContractAddress: '0xLUW…',
})
The | false return convention
Every action method resolves to its result on success, or to false when the call is
skipped because a wallet popup is already open or the user cancelled/closed the popup. false
is not an error and not the data — always branch on it:
const res = await sdk.createSRR(args)
if (res === false) {
// user cancelled the popup — nothing happened on-chain
} else {
console.log(res.tx.tokenId)
}
Unexpected failures (API / validation / RPC) throw one of the exported error classes.
Configuration
new Startrail(config?: StartrailSDKInitConfig) — every field is optional.
| Field | Type | Default | Description |
|---|---|---|---|
env |
'production' | 'staging' | 'lrc' | 'local' |
'production' |
Target environment / network (see Environments). |
wallet |
'startrail' | 'metamask' |
'startrail' |
Wallet backend. startrail = social/email login via Torus. |
apiPath |
string |
production URL | Startrail API base URL the SDK calls. |
rpcEndpoint |
string |
per env |
Custom RPC endpoint the wallet connects to. |
chainId |
number |
per env |
Custom EVM chain id. |
lang |
'en' | 'ja' |
'en' |
UI language. |
loginProvider |
LoginProvider[] |
— | Allowed providers, e.g. ['email_password'], ['google'], ['email_passwordless']. |
authAction |
{ login: boolean; signup: boolean } |
— | Restrict the email popup to a single tab and adjust wording. |
auth0TorusConfigKey |
string |
— | Auth0Torus config key (provided by Torus) used to initialise the network. |
customUi |
CustomUi |
— | UI customisation (logos, service name, wording) — see Custom UI. |
callbackUrl |
string |
— | URL the signup verification email redirects back to. |
withModal |
boolean |
false |
Show the Torus login-provider selection modal. |
loginHint |
string |
— | User's email when known. With a single email_passwordless provider, login goes straight through and skips the modal. |
debug |
boolean |
false |
Verbose SDK logging to the console. |
mfaLevel |
'none' | 'mandatory' | 'optional' |
— | Deprecated — no effect since torus-embed v6. |
torusBuildEnv |
StartrailEnv |
— | Deprecated — alias for env; use env. |
LoginProvider ∈ 'google' \| 'email_password' \| 'email_passwordless' \| 'facebook' \| 'twitter' \| 'line' \| 'apple'.
API
All methods are async. Most write actions trigger login() automatically (a wallet popup) if the
user isn't authenticated yet. startrailLUWContractAddress is the Licensed User Wallet (LUW)
contract the meta-transaction is proxied through, and is required by most write actions.
Addresses are checksummed hex; royalties are in basis points (1000 = 10%).
Auth & session
| Method | Returns |
|---|---|
login(overwriteConfig?: OverwriteStartrailSDKConfig) |
Promise<EOA[] | false> — authenticated addresses, or false if cancelled. |
logout() |
Promise<void | false> |
getUserInfo() |
Promise<UserInfo | false> — { email, name, profileImage, … }. |
signMessage(message: string, disableCustomPrefix?: boolean) |
Promise<MessageSignature | false> — set disableCustomPrefix = true to use personal_sign (preferred). |
switchLanguage(lang: Language) |
Promise<void | false> — 'en' or 'ja'. |
OverwriteStartrailSDKConfig (per-call overrides for login): { authAction?, lang?, loginProvider?, loginHint? }.
// Email known in advance + single passwordless provider → no modal shown
const eoas = await sdk.login({ loginProvider: ['email_passwordless'], loginHint: 'user@example.com' })
Collections
// Deploy a new collection (ERC-721) contract
await sdk.createCollection({
name: 'My Collection',
symbol: 'MYC',
startrailLUWContractAddress: '0xLUW…',
}) // -> { txReceiptId, tx: { contractAddress, salt } } | false
// Transfer collection ownership
await sdk.transferCollectionOwnership({
contractAddress: '0xCollection…',
newOwner: '0xNewOwner…',
startrailLUWContractAddress: '0xLUW…',
}) // -> { txReceiptId, tx: { contractAddress } } | false
Startrail Records (SRRs)
// Mint
await sdk.createSRR({
isPrimaryIssuer: true,
metadata: { name: 'Artwork #1' }, // validated against the SRR schema
artistAddress: '0xArtist…',
lockExternalTransfer: false, // true = Startrail-only transfers
startrailLUWContractAddress: '0xLUW…',
}) // -> { txReceiptId, tx: { tokenId, metadataCID, contractAddress? } } | false
// Update metadata
await sdk.updateMetadata({ tokenId: '123', metadata: { name: 'New title' }, startrailLUWContractAddress: '0xLUW…' })
// Transfer to an Ethereum address with provenance
await sdk.transferSRRToEthereumAddress({
to: '0xRecipient…',
tokenId: '123',
metadata: { transferType: 'sale' }, // HistoryMetadata
startrailLUWContractAddress: '0xLUW…',
})
// Two-step transfer (step 1 — the new owner must reveal to finalise)
await sdk.approveSRRByCommitment({ tokenId: '123', metadata: { transferType: 'sale' }, startrailLUWContractAddress: '0xLUW…' })
// Associate custom histories
await sdk.addCustomHistoriesToSRRs({ tokenIds: ['123'], customHistoryIds: ['10'], startrailLUWContractAddress: '0xLUW…' })
// Convert metadata to the latest schema (offline — no popup, no tx)
await sdk.convertMetadata({ metadataBatch: [{ metadata: { name: 'legacy' }, tokenId: '123' }] })
// Read ERC-2981 royalty (read-only)
await sdk.checkERC2981Royalty({ tokenId: '123' }) // -> { royaltyReceiver, royaltyBasisPoints } | false
Batch
// Run several operations in one transaction
await sdk.bulk({
isCompressEnabled: false,
startrailLUWContractAddress: '0xLUW…',
txs: [
{ functionType: 'createSRR', data: { /* CreateSRRForRequest */ } },
// functionType ∈ 'createSRR' | 'approveSRRByCommitment' | 'transferFromWithProvenance'
],
}) // -> { batchId } | false
Success responses carry a
txReceiptId(orbatchId); the on-chain state change is asynchronous — poll the Startrail API with that id to confirm completion.
transferFromWithProvenance(args)is deprecated — usetransferSRRToEthereumAddress(args).
Environments
Selected via env. Determines the blockchain network and the Torus/Auth0 accounts used.
env |
Network | Notes |
|---|---|---|
production |
Polygon (Matic) mainnet | Production Torus + Startrail production API. |
staging |
Amoy testnet | Production Torus + Startrail staging API. |
lrc |
Amoy testnet | LRC Torus + Startrail QA API. |
local |
http://localhost:8545 |
Local development. |
Custom UI
customUi customises logos, names, and wording. words supplies per-language overrides for the
modal, signPopup, emailAuthPopup, and embed strings.
const sdk = new Startrail({
env: 'lrc',
customUi: {
logoUrl: 'https://sample.com/logo',
logoWhiteUrl: 'https://sample.com/white-logo', // white logo on dark backgrounds
serviceName: 'Your service',
contactUrl: 'contact@startrail.io',
verificationEmailTitle: 'Your verification email title',
words: {
en: {
modal: {
// {verifier} is replaced with the chosen provider name if present
continueLogin: 'Login with previous {verifier} account',
termsConditions: 'Terms & Conditions',
termsConditionsLinkUrl: 'https://…',
privacyPolicyLinkUrl: 'https://…',
},
signPopup: { title: '…', requestFrom: '…', dataToSign: '…', confirm: 'Confirm', cancel: 'Cancel' },
emailAuthPopup: { titleLogin: 'Log in', titleSignup: 'Sign up' },
},
ja: {
modal: { continueLogin: '前回の{verifier}アカウントを使う' },
emailAuthPopup: { titleLogin: 'ログイン', titleSignup: '新規登録' },
},
},
},
})
Errors
Unexpected failures throw a subclass of a common Startrail error base, all exported from the package.
Catch the specific class or inspect error.errorCode.
import { Startrail, METADATA_VALIDATION_FAILED } from '@startbahn/startrail-sdk-js'
try {
const res = await sdk.createSRR(args)
if (res === false) { /* user cancelled the popup */ }
} catch (e) {
if (e instanceof METADATA_VALIDATION_FAILED) { /* fix the metadata */ }
else throw e
}
Common classes: StartrailApiError, STARTRAIL_API_QUOTA_LIMIT_EXCEEDED, METADATA_VALIDATION_FAILED,
Auth0VerifyEmail, WALLET_NOT_FOUND, WALLET_NOT_INITIALIZED, WALLET_EOA_NOT_MATCH,
WALLET_USER_REJECT_WALLET_REQUEST, WALLET_RPC_CONNECTION_FAILURE, TORUS_FAILS_INIT,
TORUS_THIRD_PARTY_COOKIES_UNSUPPORTED, JSON_RPC_ERROR, Others. See llms.txt for the
full list and when each is thrown.
Tips
- MetaMask: call
sdk.login()before asking the user to sign or call the API, to fetch the currently active EOA and compare it in your app — the SDK can't prevent users from switching accounts (a mismatch surfaces asWALLET_EOA_NOT_MATCH). - Read-only methods (
convertMetadata,checkERC2981Royalty) don't submit transactions. - TypeScript: types are bundled (
types/startrail-sdk-js.d.ts) — hover any method or argument for inline docs.
Resources
- Machine-readable API reference for LLMs/agents:
llms.txt - Agent how-to guide:
AGENTS.md - Local development:
docs/local_setup.md