Skip to content

Commit

Permalink
Merge pull request #212 from ardriveapp/alpha
Browse files Browse the repository at this point in the history
chore: release v1.21.0
  • Loading branch information
fedellen authored Nov 25, 2024
2 parents 023231d + 9c2fc77 commit 3607657
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

## [1.20.2](https://github.com/ardriveapp/turbo-sdk/compare/v1.20.1...v1.20.2) (2024-11-07)


Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Welcome to the `@ardrive/turbo-sdk`! This SDK provides functionality for interac
- [`getFiatRates()`](#getfiatrates)
- [`getWincForFiat({ amount })`](#getwincforfiat-amount-)
- [`getWincForToken({ tokenAmount })`](#getwincfortoken-tokenamount-)
- [`getTokenPriceForBytes({ byteCount })`](#gettokenpriceforbytes-bytecount-)
- [`getUploadCosts({ bytes })`](#getuploadcosts-bytes-)
- [`uploadSignedDataItem({ dataItemStreamFactory, dataItemSizeFactory, signal })`](#uploadsigneddataitem-dataitemstreamfactory-dataitemsizefactory-signal-)
- [`createCheckoutSession({ amount, owner })`](#createcheckoutsession-amount-owner-)
Expand Down Expand Up @@ -80,6 +81,7 @@ Welcome to the `@ardrive/turbo-sdk`! This SDK provides functionality for interac
- [`upload-folder`](#upload-folder)
- [`upload-file`](#upload-file)
- [`price`](#price)
- [`token-price`](#token-price)
- [`share-credits`](#share-credits)
- [`revoke-credits`](#revoke-credits)
- [`list-shares`](#list-shares)
Expand Down Expand Up @@ -390,6 +392,19 @@ const { winc, actualTokenAmount, equivalentWincTokenAmount } =
});
```

#### `getTokenPriceForBytes({ byteCount })`

Get the current price from the Turbo Payment Service, denominated in the specified token, for uploading a specified number of bytes to Turbo.

```typescript
const turbo = TurboFactory.unauthenticated({ token: 'solana' });
const { tokenPrice } = await turbo.getTokenPriceForBytes({
byteCount: 1024 * 1024 * 100,
});

console.log(tokenPrice); // Estimated SOL Price for 100 MiB
```

#### `getUploadCosts({ bytes })`

Returns the estimated cost in Winston Credits for the provided file sizes, including all upload adjustments and fees.
Expand Down Expand Up @@ -905,6 +920,20 @@ turbo price --value 1024 --type bytes
turbo price --value 1.1 --type arweave
```

##### `token-price`

Get the current price from the Turbo Payment Service, denominated in the specified token, for uploading a specified number of bytes to Turbo.

Command Options:

- `--byte-count <byteCount>` - Byte value to get the token price for

e.g:

```shell
turbo token-price --byte-count 102400 --token solana
```

##### `share-credits`

Shares credits from the connected wallet to the provided native address and approved winc amount.
Expand Down
10 changes: 10 additions & 0 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { listShares } from './commands/listShares.js';
import { revokeCredits } from './commands/revokeCredits.js';
import { shareCredits } from './commands/shareCredits.js';
import { tokenPrice } from './commands/tokenPrice.js';
import {
globalOptions,
listSharesOptions,
Expand Down Expand Up @@ -99,6 +100,15 @@ applyOptions(
await runCommand(command, price);
});

applyOptions(
program
.command('token-price')
.description('Get the current token price for provided byte value'),
[optionMap.byteCount],
).action(async (_commandOptions, command: Command) => {
await runCommand(command, tokenPrice);
});

applyOptions(
program
.command('share-credits')
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/shareCredits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export async function shareCredits(
const result = await turbo.shareCredits({
approvedAddress,
approvedWincAmount,
expiresBySeconds,
expiresBySeconds:
expiresBySeconds !== undefined ? +expiresBySeconds : undefined,
});

console.log(
Expand Down
54 changes: 54 additions & 0 deletions src/cli/commands/tokenPrice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 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 { isTokenType } from '../../common/index.js';
import { TurboFactory } from '../../node/factory.js';
import { tokenTypes } from '../../types.js';
import { TokenPriceOptions } from '../types.js';
import { configFromOptions } from '../utils.js';

export async function tokenPrice(options: TokenPriceOptions) {
const byteCount =
options.byteCount !== undefined ? +options.byteCount : undefined;
if (
byteCount === undefined ||
byteCount <= 0 ||
isNaN(byteCount) ||
!Number.isInteger(byteCount)
) {
throw new Error(
'Must provide a positive number for byte to get price.\nFor example, to get the SOL price for 100 MiB use the following:\nturbo token-price --token solana --byte-count 1048576000',
);
}
const token = options.token;

if (!isTokenType(token)) {
throw new Error(
`Invalid token type ${token}. Must be one of ${tokenTypes.join(', ')}`,
);
}

const turbo = TurboFactory.unauthenticated(configFromOptions(options));
const { tokenPrice } = await turbo.getTokenPriceForBytes({
byteCount: +byteCount,
});

const output = {
tokenPrice,
byteCount,
message: `The current price estimate for ${byteCount} bytes is ${tokenPrice} ${token}`,
};
console.log(JSON.stringify(output, null, 2));
}
4 changes: 4 additions & 0 deletions src/cli/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ export const optionMap = {
'Use the signer balance first before using credit share approvals',
default: false,
},
byteCount: {
alias: '--byte-count <byteCount>',
description: 'Number of bytes to use for the action',
},
} as const;

export const walletOptions = [
Expand Down
10 changes: 7 additions & 3 deletions src/cli/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,18 @@ export type UploadFolderOptions = UploadOptions & {
indexFile: string | undefined;
fallbackFile: string | undefined;
manifest: boolean;
maxConcurrency: number | undefined;
maxConcurrency: string | undefined;
};

export type UploadFileOptions = UploadOptions & {
filePath: string | undefined;
};

export type PriceOptions = GlobalOptions & {
export type TokenPriceOptions = GlobalOptions & {
byteCount: string | undefined;
};

export type PriceOptions = TokenPriceOptions & {
value: string | undefined;
type: string | undefined;
};
Expand All @@ -72,7 +76,7 @@ export type CryptoFundOptions = WalletOptions & {
export type ShareCreditsOptions = WalletOptions & {
address: string | undefined;
value: string | undefined;
expiresBySeconds: number | undefined;
expiresBySeconds: string | undefined;
};

export type RevokeCreditsOptions = WalletOptions & {
Expand Down
29 changes: 29 additions & 0 deletions src/common/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
TurboRatesResponse,
TurboSignedRequestHeaders,
TurboSubmitFundTxResponse,
TurboTokenPriceForBytesResponse,
TurboUnauthenticatedPaymentServiceConfiguration,
TurboUnauthenticatedPaymentServiceInterface,
TurboWincForFiatParams,
Expand All @@ -51,6 +52,7 @@ import {
} from '../types.js';
import { TurboHTTPService } from './http.js';
import { TurboWinstonLogger } from './logger.js';
import { exponentMap, tokenToBaseMap } from './token/index.js';

export const developmentPaymentServiceURL = 'https://payment.ardrive.dev';
export const defaultPaymentServiceURL = 'https://payment.ardrive.io';
Expand Down Expand Up @@ -287,6 +289,33 @@ export class TurboUnauthenticatedPaymentService
}
return response;
}

public async getTokenPriceForBytes({
byteCount,
}: {
byteCount: number;
}): Promise<TurboTokenPriceForBytesResponse> {
const wincPriceForOneToken = (
await this.getWincForToken({
tokenAmount: tokenToBaseMap[this.token](1),
})
).winc;
const wincPriceForOneGiB = (
await this.getUploadCosts({
bytes: [2 ** 30],
})
)[0].winc;

const tokenPriceForOneGiB = new BigNumber(wincPriceForOneGiB).dividedBy(
wincPriceForOneToken,
);
const tokenPriceForBytes = tokenPriceForOneGiB
.dividedBy(2 ** 30)
.times(byteCount)
.toFixed(exponentMap[this.token]);

return { byteCount, tokenPrice: tokenPriceForBytes, token: this.token };
}
}
// NOTE: to avoid redundancy, we use inheritance here - but generally prefer composition over inheritance
export class TurboAuthenticatedPaymentService
Expand Down
8 changes: 5 additions & 3 deletions src/common/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export abstract class TurboDataItemAbstractSigner
private ownerToNativeAddress(owner: string, token: TokenType): NativeAddress {
switch (token) {
case 'solana':
return bs58.encode(fromB64Url(owner));
return bs58.encode(Uint8Array.from(fromB64Url(owner)));

case 'ethereum':
case 'matic':
Expand All @@ -90,7 +90,9 @@ export abstract class TurboDataItemAbstractSigner
return pubkeyToAddress(
{
type: 'tendermint/PubKeySecp256k1',
value: toBase64(Secp256k1.compressPubkey(fromB64Url(owner))),
value: toBase64(
Secp256k1.compressPubkey(Uint8Array.from(fromB64Url(owner))),
),
},
'kyve',
);
Expand All @@ -104,7 +106,7 @@ export abstract class TurboDataItemAbstractSigner
public async generateSignedRequestHeaders() {
const nonce = randomBytes(16).toString('hex');
const buffer = Buffer.from(nonce);
const signature = await this.signer.sign(buffer);
const signature = await this.signer.sign(Uint8Array.from(buffer));
const publicKey = toB64Url(this.signer.publicKey);
return {
'x-public-key': publicKey,
Expand Down
9 changes: 9 additions & 0 deletions src/common/token/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ export const defaultTokenMap: TokenFactory = {
pol: (config: TokenConfig) => new PolygonToken(config),
} as const;

export const exponentMap: Record<TokenType, number> = {
arweave: 12,
solana: 9,
ethereum: 18,
kyve: 6,
matic: 18,
pol: 18,
} as const;

export const tokenToBaseMap: Record<
TokenType,
(a: BigNumber.Value) => BigNumber.Value
Expand Down
12 changes: 12 additions & 0 deletions src/common/turbo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
TurboRevokeCreditsParams,
TurboSignedDataItemFactory,
TurboSubmitFundTxResponse,
TurboTokenPriceForBytesResponse,
TurboUnauthenticatedClientConfiguration,
TurboUnauthenticatedClientInterface,
TurboUnauthenticatedPaymentServiceInterface,
Expand Down Expand Up @@ -169,6 +170,17 @@ export class TurboUnauthenticatedClient
return this.paymentService.getWincForToken(params);
}

/**
* Determines the price in the instantiated token to upload one data item of a specific size in bytes, including all Turbo cost adjustments and fees.
*/
getTokenPriceForBytes({
byteCount,
}: {
byteCount: number;
}): Promise<TurboTokenPriceForBytesResponse> {
return this.paymentService.getTokenPriceForBytes({ byteCount });
}

/**
* Uploads a signed data item to the Turbo Upload Service.
*/
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ export type TurboWincForTokenResponse = Omit<
equivalentWincTokenAmount: string;
};

export type TurboTokenPriceForBytesResponse = {
tokenPrice: string;
byteCount: number;
token: TokenType;
};

export type TurboWincForFiatParams = {
amount: CurrencyMap;
nativeAddress?: NativeAddress;
Expand Down Expand Up @@ -580,6 +586,11 @@ export interface TurboUnauthenticatedPaymentServiceInterface {
getWincForToken(
params: TurboWincForTokenParams,
): Promise<TurboWincForTokenResponse>;
getTokenPriceForBytes({
byteCount,
}: {
byteCount: number;
}): Promise<TurboTokenPriceForBytesResponse>;
getUploadCosts({ bytes }: { bytes: number[] }): Promise<TurboPriceResponse[]>;
createCheckoutSession(
params: TurboCheckoutSessionParams,
Expand Down
4 changes: 2 additions & 2 deletions src/utils/base64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export function fromB64Url(input: Base64String): Buffer {
}

export function toB64Url(buffer: Buffer): Base64String {
return bufferTob64Url(buffer);
return bufferTob64Url(Uint8Array.from(buffer));
}

export function sha256B64Url(input: Buffer): Base64String {
return toB64Url(createHash('sha256').update(input).digest());
return toB64Url(createHash('sha256').update(Uint8Array.from(input)).digest());
}
1 change: 1 addition & 0 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function createTurboSigner({
case 'solana':
return new HexSolanaSigner(clientProvidedPrivateKey);
case 'ethereum':
case 'pol':
case 'matic':
if (!isEthPrivateKey(clientProvidedPrivateKey)) {
throw new Error(
Expand Down
24 changes: 23 additions & 1 deletion tests/turbo.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ import {
} from '../src/common/turbo.js';
import { TurboFactory } from '../src/node/factory.js';
import { TurboNodeSigner } from '../src/node/signer.js';
import { NativeAddress, TokenType, TurboSigner } from '../src/types.js';
import {
NativeAddress,
TokenType,
TurboSigner,
tokenTypes,
} from '../src/types.js';
import { signerFromKyveMnemonic } from '../src/utils/common.js';
import { FailedRequestError } from '../src/utils/errors.js';
import {
Expand Down Expand Up @@ -337,6 +342,23 @@ describe('Node environment', () => {
expect(fees).to.have.length(1);
});

describe('getTokenPriceForBytes()', async () => {
const bytes = 1024 * 1024 * 100; // 100 MiB
for (const token of tokenTypes) {
it(`should return the correct token price for the given bytes for ${token}`, async () => {
const { tokenPrice, byteCount: bytesResult } =
await TurboFactory.unauthenticated({
token,
}).getTokenPriceForBytes({
byteCount: bytes,
});
expect(tokenPrice).to.not.be.undefined;
expect(bytesResult).to.equal(bytes);
expect(+tokenPrice).to.be.a('number');
});
}
});

describe('uploadSignedDataItem()', () => {
const signer = new ArweaveSigner(testJwk);
it('should properly upload a signed Buffer to turbo', async () => {
Expand Down

0 comments on commit 3607657

Please sign in to comment.