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
JavaScript
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;
}
});
}
}