UNPKG

freelii-passkey-kit

Version:

A helper library for creating and using smart wallet accounts on the Stellar blockchain.

167 lines (166 loc) 6.81 kB
import { xdr } from "@stellar/stellar-sdk/minimal"; import { PasskeyBase } from "./base"; import base64url from "base64url"; import { AssembledTransaction } from "@stellar/stellar-sdk/minimal/contract"; import { Durability } from "@stellar/stellar-sdk/minimal/rpc"; // Note: Hardcoded version to avoid ESM import issues with JSON const version = '0.10.46'; import { LoggingService } from './logging'; // TODO set default headers in constructor export class PasskeyServer extends PasskeyBase { launchtubeJwt; mercuryJwt; mercuryKey; launchtubeUrl; launchtubeHeaders; mercuryProjectName; mercuryUrl; constructor(options) { const { rpcUrl, launchtubeUrl, launchtubeJwt, launchtubeHeaders, mercuryProjectName, mercuryUrl, mercuryJwt, mercuryKey, logging, } = options; super(rpcUrl); if (logging) LoggingService.init(logging); if (launchtubeUrl) this.launchtubeUrl = launchtubeUrl; if (launchtubeJwt) this.launchtubeJwt = launchtubeJwt; if (launchtubeHeaders) this.launchtubeHeaders = launchtubeHeaders; if (mercuryProjectName) this.mercuryProjectName = mercuryProjectName; if (mercuryUrl) this.mercuryUrl = mercuryUrl; if (mercuryJwt) this.mercuryJwt = mercuryJwt; if (mercuryKey) this.mercuryKey = mercuryKey; } async getSigners(contractId) { if (!this.rpc || !this.mercuryProjectName || !this.mercuryUrl || (!this.mercuryJwt && !this.mercuryKey)) { const logger = LoggingService.get(); logger.error('Mercury service not configured'); throw new Error('Mercury service not configured'); } const signers = await fetch(`${this.mercuryUrl}/zephyr/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: this.mercuryJwt ? `Bearer ${this.mercuryJwt}` : this.mercuryKey }, body: JSON.stringify({ project_name: this.mercuryProjectName, mode: { Function: { fname: "get_signers_by_address", arguments: JSON.stringify({ address: contractId }) } } }) }) .then(async (res) => { const response = await res.json(); if (res.ok) { const logger = LoggingService.get(); logger.info({ message: 'server.get_signers.success', contractId }); return response; } const logger = LoggingService.get(); logger.error({ message: 'server.get_signers.error', contractId, error: response }); throw response; }); for (const signer of signers) { if (signer.storage === 'Temporary') { try { await this.rpc.getContractData(contractId, xdr.ScVal.scvBytes(base64url.toBuffer(signer.key)), Durability.Temporary); } catch (error) { const logger = LoggingService.get(); logger.debug({ message: 'server.get_signer.evicted', contractId, signerKey: signer.key, error: error instanceof Error ? error.message : String(error) }); signer.evicted = true; } } } return signers; } async getContractId(options, index = 0) { if (!this.mercuryProjectName || !this.mercuryUrl || (!this.mercuryJwt && !this.mercuryKey)) throw new Error('Mercury service not configured'); let { keyId, publicKey, policy } = options || {}; if ([keyId, publicKey, policy].filter((arg) => !!arg).length > 1) throw new Error('Exactly one of `options.keyId`, `options.publicKey`, or `options.policy` must be provided.'); let args; if (keyId) args = { key: keyId, kind: 'Secp256r1' }; else if (publicKey) args = { key: publicKey, kind: 'Ed25519' }; else if (policy) args = { key: policy, kind: 'Policy' }; const res = await fetch(`${this.mercuryUrl}/zephyr/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: this.mercuryJwt ? `Bearer ${this.mercuryJwt}` : this.mercuryKey }, body: JSON.stringify({ project_name: this.mercuryProjectName, mode: { Function: { fname: "get_addresses_by_signer", arguments: JSON.stringify(args) } } }) }) .then(async (res) => { if (res.ok) return await res.json(); throw await res.json(); }); return res[index]; } /* LATER - Add a method for getting a paginated or filtered list of all a wallet's events */ async send(txn, fee) { const logger = LoggingService.get(); if (!this.launchtubeUrl) { logger.error('Launchtube service not configured'); throw new Error('Launchtube service not configured'); } const data = new FormData(); if (txn instanceof AssembledTransaction) { txn = txn.built.toXDR(); } else if (typeof txn !== 'string') { txn = txn.toXDR(); } data.set('xdr', txn); if (fee) data.set('fee', fee.toString()); let lt_headers = Object.assign({ 'X-Client-Name': 'passkey-kit', 'X-Client-Version': version, }, this.launchtubeHeaders); if (this.launchtubeJwt) lt_headers.authorization = `Bearer ${this.launchtubeJwt}`; logger.info({ message: 'server.send', xdr: txn, fee, launchtubeUrl: this.launchtubeUrl, rpcUrl: this.rpcUrl, mercuryProjectName: this.mercuryProjectName, mercuryUrl: this.mercuryUrl, }); return fetch(this.launchtubeUrl, { method: 'POST', headers: lt_headers, body: data }).then(async (res) => { if (res.ok) { const response = await res.json(); logger.info({ message: 'server.send.success', xdr: txn, status: res.status, response }); return response; } else { const error = await res.json(); logger.error({ message: 'server.send.error', xdr: txn, status: res.status, error }); throw error; } }); } }