UNPKG

@ar.io/sdk

Version:

[![codecov](https://codecov.io/gh/ar-io/ar-io-sdk/graph/badge.svg?token=7dXKcT7dJy)](https://codecov.io/gh/ar-io/ar-io-sdk)

203 lines (202 loc) 7.39 kB
/** * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { ArconnectSigner, ArweaveSigner, EthereumSigner, InjectedEthereumSigner, SignatureConfig, } from '@dha-team/arbundles'; import { v4 as uuidv4 } from 'uuid'; import { mARIOToken } from '../types/token.js'; import { toB64Url } from '../utils/base64.js'; import { createAxiosInstance } from '../utils/http-client.js'; import { urlWithSearchParams } from '../utils/url.js'; import { Logger } from './logger.js'; export async function signedRequestHeadersFromSigner({ signer, nonce = uuidv4(), }) { let signature = undefined; let publicKey = undefined; const signatureType = isWanderArweaveBrowserSigner(signer) ? SignatureConfig.ARWEAVE : signer.signatureType; // equivalent to window.arweaveWallet if (isWanderArweaveBrowserSigner(signer)) { signature = toB64Url(Buffer.from(await signer.signMessage(Uint8Array.from(Buffer.from(nonce))))); } else if (signer instanceof ArconnectSigner) { signature = toB64Url(Buffer.from(await signer['signer'].signMessage(Uint8Array.from(Buffer.from(nonce))))); } else if (signer instanceof ArweaveSigner || signer instanceof EthereumSigner || signer instanceof InjectedEthereumSigner) { if ('setPublicKey' in signer && signer['publicKey'] === undefined) { await signer.setPublicKey(); } signature = toB64Url(Buffer.from(await signer.sign(Uint8Array.from(Buffer.from(nonce))))); } switch (signatureType) { case SignatureConfig.ARWEAVE: if (isWanderArweaveBrowserSigner(signer)) { publicKey = await signer.getActivePublicKey(); } else if ('setPublicKey' in signer) { await signer.setPublicKey(); publicKey = toB64Url(signer.publicKey); } break; case SignatureConfig.ETHEREUM: if ('publicKey' in signer) { publicKey = '0x' + signer.publicKey.toString('hex'); } else { throw new Error('Public key not found'); } break; // TODO: solana sig support // case SignatureConfig.SOLANA: // case SignatureConfig.ED25519: default: throw new Error(`Unsupported signer type for signing requests: ${signatureType}`); } if (publicKey === undefined || signature === undefined) { throw new Error('Public key or signature not found'); } return { 'x-public-key': publicKey, 'x-nonce': nonce, 'x-signature': signature, 'x-signature-type': signatureType.toString(), }; } export class TurboArNSPaymentFactory { static init(config) { const { signer, paymentUrl, axios, logger } = config ?? {}; if (signer !== undefined) { return new TurboArNSPaymentProviderAuthenticated({ signer, paymentUrl, axios, logger, }); } return new TurboArNSPaymentProviderUnauthenticated({ paymentUrl, axios, logger, }); } } // Base class for unauthenticated operations export class TurboArNSPaymentProviderUnauthenticated { paymentUrl; axios; logger; constructor({ paymentUrl = 'https://payment.ardrive.io', axios = createAxiosInstance(), logger = Logger.default, }) { this.paymentUrl = paymentUrl; this.axios = axios; this.logger = logger; } async getArNSPriceDetails({ intent, name, quantity, type, years, }) { const url = urlWithSearchParams({ baseUrl: `${this.paymentUrl}/v1/arns/price/${intent}/${name}`, params: { increaseQty: quantity, type, years, }, }); const { data, status } = await this.axios.get(url); this.logger.debug('getArNSPriceDetails', { intent, name, quantity, type, years, data, status, }); if (status !== 200) { throw new Error('Failed to get ArNS purchase price ' + JSON.stringify(data)); } if (!data.winc || !data.mARIO) { throw new Error('Invalid response from Turbo ' + JSON.stringify(data)); } return { winc: data.winc, mARIO: new mARIOToken(+data.mARIO), }; } async getPrice(params) { const { winc } = await this.getArNSPriceDetails(params); return +winc; } } // Class for authenticated operations, extending the base class export class TurboArNSPaymentProviderAuthenticated extends TurboArNSPaymentProviderUnauthenticated { signer; constructor({ signer, ...restConfig }) { super(restConfig); // Pass unauthenticated config to base class+ if (!isTurboArNSSigner(signer)) { throw new Error('Signer must be a TurboArNSSigner'); } this.signer = signer; } async initiateArNSPurchase({ intent, name, quantity, type, processId, years, paidBy = [], referrer, }) { // Signer check is implicitly handled by requiring it in the constructor const url = urlWithSearchParams({ baseUrl: `${this.paymentUrl}/v1/arns/purchase/${intent}/${name}`, params: { increaseQty: quantity, processId, type, years, paidBy, referrer, }, }); const headers = await signedRequestHeadersFromSigner({ signer: this.signer, }); const { data, status } = await this.axios.post(url, null, { headers, }); this.logger.debug('Initiated ArNS purchase', { intent, name, quantity, processId, type, years, data, status, }); if (status !== 200) { throw new Error('Failed to initiate ArNS purchase ' + JSON.stringify(data)); } return { id: data.arioWriteResult.id, result: data.purchaseReceipt, }; } } function isWanderArweaveBrowserSigner(signer) { return (typeof signer === 'object' && signer !== null && 'signMessage' in signer && 'getActivePublicKey' in signer); } export function isTurboArNSSigner(signer) { const isWanderWallet = isWanderArweaveBrowserSigner(signer); const isSigner = signer instanceof EthereumSigner || signer instanceof InjectedEthereumSigner || signer instanceof ArweaveSigner || signer instanceof ArconnectSigner; return isWanderWallet || isSigner; }