attest.so
Version:
<div align="center">
358 lines (316 loc) • 12.1 kB
text/typescript
import { AttestSDKBase } from './base'
import {
AttestationConfig,
AttestSDKResponse,
RevokeAttestationConfig,
SchemaConfig,
StellarConfig,
StellarFetchAuthorityResult,
StellarFetchSchemaResult,
StellarFetchAttestationResult,
SolanaRevokeAttestationConfig,
StellarAttestationConfig
} from './types'
import * as StellarSdk from '@stellar/stellar-sdk';
/**
* Stellar implementation of the Attest SDK
*/
export class StellarAttestSDK extends AttestSDKBase {
private server: StellarSdk.Horizon.Server
private keypair: StellarSdk.Keypair
private networkPassphrase: string
private ATTEST_CONTRACT = "CAF5SWYR7B7V5FYUXTGYXCRUNRQEIWEUZRDCARNMX456LRD64RX76BNN"
private AUTHORITY_CONTRACT = "CBORL365DYHJLIOUXR7GU6VIKXQ4X7DJTV6SR366O6ZWCF7C5PO2XESQ"
/**
* Creates a new instance of the Stellar Attest SDK
* @param config SDK configuration options
*/
constructor(config: StellarConfig) {
super()
// Initialize Stellar SDK
this.server = new StellarSdk.Horizon.Server(config.url ?? 'https://horizon-testnet.stellar.org')
this.keypair = StellarSdk.Keypair.fromSecret(config.secretKey)
this.networkPassphrase = config.networkPassphrase ?? StellarSdk.Networks.TESTNET
}
/**
* Initialize the SDK by setting the admin of the contract
* Maps to initialize(env: Env, admin: Address) in the Stellar contract
*/
async initialize(): Promise<AttestSDKResponse<void>> {
try {
// Create contract instance
const contract = new StellarSdk.Contract(this.contractId)
// Call initialize to set admin
// const operation = contract.call("initialize", [
// new StellarSdk.Address(this.keypair.publicKey()).toString()
// ])
// // Execute transaction
// await this.buildAndSubmitTransaction([operation])
return { data: undefined }
} catch (error) {
return { error }
}
}
/**
* Retrieves the authority record for the current wallet
* Note: The Stellar contract doesn't have a direct method for this
* @returns The authority record or null if not found
*/
async fetchAuthority(): Promise<AttestSDKResponse<StellarFetchAuthorityResult | null>> {
try {
// In Stellar contract, authority is implicit with the caller's address
// There's no separate authority record storage like in Solana
// We return the current keypair information
return {
data: {
address: this.keypair.publicKey(),
metadata: "Default authority metadata"
}
}
} catch (error) {
return { error }
}
}
/**
* Registers the current wallet as an authority
* Note: In Stellar, there's no explicit registration needed beyond initialization
* @returns The authority address
*/
async registerAuthority(): Promise<AttestSDKResponse<string>> {
try {
// In Stellar's contract, there's no separate registration needed
// The initialize method sets the admin, and all other operations work based on caller address
// We're just returning the public key here
return { data: this.keypair.publicKey() }
} catch (error) {
return { error }
}
}
/**
* Fetches a schema by its UID
* Maps to get_schema in the Stellar contract (internal method)
* @param schemaUID The schema UID to fetch
* @returns The schema or null if not found
*/
async fetchSchema(schemaUID: string): Promise<AttestSDKResponse<StellarFetchSchemaResult | null>> {
try {
// Create a contract instance
const contract = new StellarSdk.Contract(this.contractId)
// Currently there's no direct method to fetch a schema in the public interface
// This would need to be added to the contract for a complete implementation
// For now we return a simulated result
return {
data: {
uid: schemaUID,
definition: "Sample schema definition",
authority: this.keypair.publicKey(),
revocable: true,
resolver: null
}
}
} catch (error) {
return { error }
}
}
/**
* Creates a new schema
* Maps to register(env, caller, schema_definition, resolver, revocable) in the Stellar contract
* @param config Schema configuration
* @returns The UID of the created schema
*/
async createSchema(config: SchemaConfig): Promise<AttestSDKResponse<string>> {
try {
// Create a contract instance
const contract = new StellarSdk.Contract(this.contractId)
// Build the transaction to create a schema
// const operation = contract.call("register", [
// new StellarSdk.Address(this.keypair.publicKey()).toString(),
// config.schemaContent,
// config.resolverAddress ? new StellarSdk.Address(config.resolverAddress.toString()).toString() : null,
// config.revocable ?? true
// ])
// Execute the transaction
// const transaction = await this.buildAndSubmitTransaction([operation])
// In a real implementation, we would extract the schema UID from the transaction result
// For now, we'll use a deterministic method to generate it based on inputs
const schemaUID = this.generateSchemaUID(config.schemaName, this.keypair.publicKey())
return { data: schemaUID }
} catch (error) {
return { error }
}
}
/**
* Fetches an attestation by its identifiers
* Maps to get_attestation(env, schema_uid, subject, reference) in the Stellar contract
* @param attestation The attestation ID (encoded combination of schema_uid, subject, reference)
* @returns The attestation or null if not found
*/
async fetchAttestation(
attestation: string
): Promise<AttestSDKResponse<StellarFetchAttestationResult | null>> {
try {
// Create a contract instance
const contract = new StellarSdk.Contract(this.contractId)
// Parse the attestation parameter to extract components
const [schemaUID, subject, reference] = this.parseAttestationId(attestation)
// Call the get_attestation method
// const operation = contract.call("get_attestation", [
// schemaUID,
// new StellarSdk.Address(subject).toString(),
// reference
// ])
// In a real implementation, we would execute this and parse the result
// For now, return simulated data
return {
data: {
schemaUid: schemaUID,
subject,
value: "Sample attestation value",
reference,
revoked: false
}
}
} catch (error) {
return { error }
}
}
/**
* Creates a new attestation
* Maps to attest(env, caller, schema_uid, subject, value, reference) in the Stellar contract
* @param config Attestation configuration
* @returns The ID of the created attestation
*/
async attest(config: StellarAttestationConfig): Promise<AttestSDKResponse<string>> {
try {
// Create a contract instance
const contract = new StellarSdk.Contract(this.contractId)
// Build the transaction to create an attestation
// const operation = contract.call("attest", [
// new StellarSdk.Address(this.keypair.publicKey()).toString(),
// config.schemaData.toString(),
// new StellarSdk.Address(config.accounts.recipient.toString()).toString(),
// config.data,
// config.refUID ? config.refUID.toString() : null
// ])
// // Execute the transaction
// const transaction = await this.buildAndSubmitTransaction([operation])
// Generate the attestation ID - in practice this would be derived from contract data
const attestationId = this.generateAttestationId(
config.schemaData.toString(),
config.accounts.recipient.toString(),
config.refUID?.toString() ?? null
)
return { data: attestationId }
} catch (error) {
return { error }
}
}
/**
* Revokes an attestation
* Maps to revoke_attestation(env, caller, schema_uid, subject, reference) in the Stellar contract
* @param props Revocation configuration
* @returns The ID of the revoked attestation
*/
async revokeAttestation(
props: RevokeAttestationConfig
): Promise<AttestSDKResponse<string>> {
try {
// Create a contract instance
const contract = new StellarSdk.Contract(this.contractId)
// Build the transaction to revoke the attestation
// const operation = contract.call("revoke_attestation", [
// new StellarSdk.Address(this.keypair.publicKey()).toString(),
// props.schemaUID.toString(),
// new StellarSdk.Address(props.recipient.toString()).toString(),
// props.reference || null
// ])
// Execute the transaction
// const transaction = await this.buildAndSubmitTransaction([operation])
// Generate the attestation ID
const attestationId = this.generateAttestationId(
props.schemaUID.toString(),
props.recipient.toString(),
props.reference || null
)
return { data: attestationId }
} catch (error) {
return { error }
}
}
/**
* Helper method to build and submit a transaction
* @param operations Transaction operations
* @returns Transaction result
*/
private async buildAndSubmitTransaction(operations: StellarSdk.Operation[]): Promise<any> {
try {
// Load the account
const account = await this.server.loadAccount(this.keypair.publicKey())
// Build the transaction
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: this.networkPassphrase
})
// Add operations
// operations.forEach(operation => {
// transaction.addOperation(operation)
// })
// Finalize and sign
const builtTx = transaction.setTimeout(30).build()
builtTx.sign(this.keypair)
// Submit
const result = await this.server.submitTransaction(builtTx)
return result
} catch (error) {
throw error
}
}
/**
* Generates a schema UID (this is a simplified simulation)
* @param schemaName Schema name
* @param authority Authority address
* @returns Generated schema UID
*/
private generateSchemaUID(schemaName: string, authority: string): string {
// This is a simplified way to generate schema UID
// In practice, this would use proper cryptographic hashing similar to the Stellar contract
return Buffer.from(`schema:${schemaName}:${authority}`).toString('hex')
}
/**
* Generates an attestation ID (this is a simplified simulation)
* @param schemaUID Schema UID
* @param subject Subject address
* @param reference Optional reference string
* @returns Generated attestation ID
*/
private generateAttestationId(schemaUID: string, subject: string, reference: string | null): string {
// This is a simplified way to generate attestation ID
// In practice, this would use proper cryptographic hashing
return Buffer.from(`attestation:${schemaUID}:${subject}:${reference || ''}`).toString('hex')
}
/**
* Parses an attestation ID into its components
* @param attestationId Attestation ID to parse
* @returns [schemaUID, subject, reference]
*/
private parseAttestationId(attestationId: string): [string, string, string | null] {
// This is a simplified parsing implementation
// In production, this would properly decode the attestation ID
// For demonstration, we'll assume the attestation ID follows our encoding pattern
try {
const decoded = Buffer.from(attestationId, 'hex').toString()
const parts = decoded.split(':')
if (parts.length >= 4) {
return [parts[1], parts[2], parts[3] || null]
}
} catch (error) {
// If parsing fails, use default values
}
// Default fallback values
return [
'schema_uid_placeholder',
'subject_placeholder',
null
]
}
}