@ar.io/sdk
Version:
[](https://codecov.io/gh/ar-io/ar-io-sdk)
203 lines (202 loc) • 7.39 kB
JavaScript
/**
* 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;
}