From 2ab2486b98930db25cf74d9ba84f2ffeef989920 Mon Sep 17 00:00:00 2001 From: Derek Sonnenberg Date: Mon, 30 Sep 2024 11:23:25 -0500 Subject: [PATCH 1/6] feat(web): implement walletAdapter for SOL and ETH web signing support PE-6052 --- src/common/factory.ts | 141 +++++++++++++++++++++++++++++++++++++++++- src/common/signer.ts | 25 ++++++++ src/node/factory.ts | 84 ++++++++----------------- src/types.ts | 48 +++++++++++++- src/web/factory.ts | 86 ++++++++------------------ 5 files changed, 262 insertions(+), 122 deletions(-) diff --git a/src/common/factory.ts b/src/common/factory.ts index 27c0de93..5ebeb991 100644 --- a/src/common/factory.ts +++ b/src/common/factory.ts @@ -13,10 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TurboUnauthenticatedConfiguration } from '../types.js'; +import { + EthereumSigner, + HexInjectedSolanaSigner, + HexSolanaSigner, + InjectedEthereumSigner, +} from '@dha-team/arbundles'; + +import { + TokenType, + TurboAuthenticatedConfiguration, + TurboAuthenticatedUploadServiceConfiguration, + TurboAuthenticatedUploadServiceInterface, + TurboSigner, + TurboUnauthenticatedConfiguration, + TurboWallet, + WalletAdapter, + isEthereumWalletAdapter, + isSolanaWalletAdapter, +} from '../types.js'; import { TurboWinstonLogger } from './logger.js'; -import { TurboUnauthenticatedPaymentService } from './payment.js'; -import { TurboUnauthenticatedClient } from './turbo.js'; +import { + TurboAuthenticatedPaymentService, + TurboUnauthenticatedPaymentService, +} from './payment.js'; +import { TurboDataItemAbstractSigner } from './signer.js'; +import { defaultTokenMap } from './token/index.js'; +import { + TurboAuthenticatedClient, + TurboUnauthenticatedClient, +} from './turbo.js'; import { TurboUnauthenticatedUploadService } from './upload.js'; export abstract class TurboBaseFactory { @@ -39,6 +65,8 @@ export abstract class TurboBaseFactory { }: TurboUnauthenticatedConfiguration = {}) { token = token === 'pol' ? 'matic' : token; + token ??= 'arweave'; // default to arweave if token is not provided + const paymentService = new TurboUnauthenticatedPaymentService({ ...paymentServiceConfig, logger: this.logger, @@ -54,4 +82,111 @@ export abstract class TurboBaseFactory { paymentService, }); } + + protected abstract getSigner({ + providedPrivateKey, + providedSigner, + providedWalletAdapter, + logger, + token, + }: { + providedSigner: TurboSigner | undefined; + providedPrivateKey: TurboWallet | undefined; + providedWalletAdapter: WalletAdapter | undefined; + token: TokenType; + logger: TurboWinstonLogger; + }): TurboDataItemAbstractSigner; + + protected abstract getAuthenticatedUploadService( + config: TurboAuthenticatedUploadServiceConfiguration, + ): TurboAuthenticatedUploadServiceInterface; + + protected getAuthenticatedTurbo({ + privateKey, + signer: providedSigner, + paymentServiceConfig = {}, + uploadServiceConfig = {}, + token, + gatewayUrl, + tokenMap, + tokenTools, + logger, + walletAdapter, + }: TurboAuthenticatedConfiguration & { logger: TurboWinstonLogger }) { + token = token === 'pol' ? 'matic' : token; + + if (!token) { + if (providedSigner) { + // Derive token from signer if not provided + if (providedSigner instanceof EthereumSigner) { + token = 'ethereum'; + } else if (providedSigner instanceof HexSolanaSigner) { + token = 'solana'; + } else { + token = 'arweave'; + } + } else { + token = 'arweave'; + } + } + + if (walletAdapter) { + if (token === 'solana') { + if (!isSolanaWalletAdapter(walletAdapter)) { + throw new Error( + 'Solana wallet adapter must implement publicKey and signMessage', + ); + } + providedSigner = new HexInjectedSolanaSigner(walletAdapter); + } + + if (token == 'ethereum') { + if (!isEthereumWalletAdapter(walletAdapter)) { + throw new Error('Ethereum wallet adapter must implement getSigner'); + } + providedSigner = new InjectedEthereumSigner(walletAdapter); + } + throw new Error( + 'Wallet adapter is currently only supported for Solana and Ethereum', + ); + } + + const turboSigner = this.getSigner({ + providedSigner, + providedPrivateKey: privateKey, + token, + logger, + providedWalletAdapter: walletAdapter, + }); + + token ??= 'arweave'; // default to arweave if token is not provided + if (!tokenTools) { + if (tokenMap && token === 'arweave') { + tokenTools = tokenMap.arweave; + } + tokenTools = defaultTokenMap[token]?.({ + gatewayUrl, + logger, + }); + } + + const paymentService = new TurboAuthenticatedPaymentService({ + ...paymentServiceConfig, + signer: turboSigner, + logger, + token, + tokenTools, + }); + const uploadService = this.getAuthenticatedUploadService({ + ...uploadServiceConfig, + signer: turboSigner, + logger, + token, + }); + return new TurboAuthenticatedClient({ + uploadService, + paymentService, + signer: turboSigner, + }); + } } diff --git a/src/common/signer.ts b/src/common/signer.ts index b72ea714..0998a041 100644 --- a/src/common/signer.ts +++ b/src/common/signer.ts @@ -37,6 +37,8 @@ import { TurboLogger, TurboSignedDataItemFactory, TurboSigner, + WalletAdapter, + isEthereumWalletAdapter, } from '../types.js'; import { fromB64Url, @@ -60,15 +62,18 @@ export abstract class TurboDataItemAbstractSigner protected logger: TurboLogger; protected signer: TurboSigner; protected token: TokenType; + protected walletAdapter: WalletAdapter | undefined; constructor({ signer, logger = TurboWinstonLogger.default, token, + walletAdapter, }: TurboDataItemSignerParams) { this.logger = logger; this.signer = signer; this.token = token; + this.walletAdapter = walletAdapter; } private ownerToNativeAddress(owner: string, token: TokenType): NativeAddress { @@ -125,6 +130,26 @@ export abstract class TurboDataItemAbstractSigner amount, gatewayUrl, }: SendTxWithSignerParams): Promise { + if (this.walletAdapter) { + if (!isEthereumWalletAdapter(this.walletAdapter)) { + throw new Error( + 'Unsupported wallet adapter -- must implement getSigner', + ); + } + const signer = this.walletAdapter.getSigner(); + if (signer.sendTransaction === undefined) { + throw new Error( + 'Unsupported wallet adapter -- getSigner must return a signer with sendTransaction API for crypto funds transfer', + ); + } + + const { hash } = await signer.sendTransaction({ + to: target, + value: parseEther(amount.toFixed(18)), + }); + return hash; + } + if (!(this.signer instanceof EthereumSigner)) { throw new Error( 'Only EthereumSigner is supported for sendTransaction API currently!', diff --git a/src/node/factory.ts b/src/node/factory.ts index 9ea506cb..275a4a6a 100644 --- a/src/node/factory.ts +++ b/src/node/factory.ts @@ -13,40 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EthereumSigner, HexSolanaSigner } from '@dha-team/arbundles'; - import { TurboBaseFactory } from '../common/factory.js'; -import { defaultTokenMap } from '../common/index.js'; -import { TurboAuthenticatedPaymentService } from '../common/payment.js'; import { TurboDataItemAbstractSigner } from '../common/signer.js'; -import { TurboAuthenticatedClient } from '../common/turbo.js'; import { - TokenType, + GetTurboSignerParams, TurboAuthenticatedConfiguration, - TurboSigner, - TurboWallet, + TurboAuthenticatedUploadServiceConfiguration, + TurboAuthenticatedUploadServiceInterface, } from '../types.js'; import { createTurboSigner } from '../utils/common.js'; import { TurboNodeSigner } from './signer.js'; import { TurboAuthenticatedUploadService } from './upload.js'; export class TurboFactory extends TurboBaseFactory { - protected static getSigner( - providedSigner: TurboSigner | undefined, - providedPrivateKey: TurboWallet | undefined, - token: TokenType, - ): TurboDataItemAbstractSigner { + protected getSigner({ + logger, + providedPrivateKey, + providedSigner, + providedWalletAdapter, + token, + }: GetTurboSignerParams): TurboDataItemAbstractSigner { return new TurboNodeSigner({ signer: createTurboSigner({ signer: providedSigner, privateKey: providedPrivateKey, token, }), - logger: this.logger, + logger, token, + walletAdapter: providedWalletAdapter, }); } + protected getAuthenticatedUploadService( + config: TurboAuthenticatedUploadServiceConfiguration, + ): TurboAuthenticatedUploadServiceInterface { + // Import the TurboAuthenticatedUploadService class from the node upload module + return new TurboAuthenticatedUploadService(config); + } + static authenticated({ privateKey, signer: providedSigner, @@ -57,53 +62,16 @@ export class TurboFactory extends TurboBaseFactory { gatewayUrl, tokenTools, }: TurboAuthenticatedConfiguration) { - token = token === 'pol' ? 'matic' : token; - - if (!token) { - if (providedSigner) { - // Derive token from signer if not provided - if (providedSigner instanceof EthereumSigner) { - token = 'ethereum'; - } else if (providedSigner instanceof HexSolanaSigner) { - token = 'solana'; - } else { - token = 'arweave'; - } - } else { - token = 'arweave'; - } - } - - const turboSigner = this.getSigner(providedSigner, privateKey, token); - - if (!tokenTools) { - if (tokenMap && token === 'arweave') { - tokenTools = tokenMap.arweave; - } - - tokenTools = defaultTokenMap[token]?.({ - gatewayUrl, - logger: this.logger, - }); - } - - const paymentService = new TurboAuthenticatedPaymentService({ - ...paymentServiceConfig, - signer: turboSigner, - logger: this.logger, + return new TurboFactory().getAuthenticatedTurbo({ + privateKey, + signer: providedSigner, + paymentServiceConfig, + uploadServiceConfig, token, + tokenMap, + gatewayUrl, tokenTools, - }); - const uploadService = new TurboAuthenticatedUploadService({ - ...uploadServiceConfig, - signer: turboSigner, logger: this.logger, - token, - }); - return new TurboAuthenticatedClient({ - uploadService, - paymentService, - signer: turboSigner, }); } } diff --git a/src/types.ts b/src/types.ts index 61984dfc..aca6c86b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,15 +18,19 @@ import { ArweaveSigner, DataItemCreateOptions, EthereumSigner, + HexInjectedSolanaSigner, HexSolanaSigner, + InjectedEthereumSigner, } from '@dha-team/arbundles'; import { IAxiosRetryConfig } from 'axios-retry'; import { BigNumber } from 'bignumber.js'; +import { JsonRpcSigner } from 'ethers'; import { Readable } from 'node:stream'; import { ReadableStream } from 'node:stream/web'; import { CurrencyMap } from './common/currency.js'; import { JWKInterface } from './common/jwk.js'; +import { TurboWinstonLogger } from './common/logger.js'; export type Base64String = string; export type NativeAddress = string; @@ -358,7 +362,9 @@ export type TurboSigner = | ArconnectSigner | ArweaveSigner | EthereumSigner - | HexSolanaSigner; + | InjectedEthereumSigner + | HexSolanaSigner + | HexInjectedSolanaSigner; export type TokenPollingOptions = { maxAttempts: number; @@ -370,11 +376,50 @@ export type TurboAuthenticatedConfiguration = TurboUnauthenticatedConfiguration & { privateKey?: TurboWallet; signer?: TurboSigner; + walletAdapter?: SolanaWalletAdapter | EthereumWalletAdapter; /** @deprecated -- This parameter was added in release v1.5 for injecting an arweave TokenTool. Instead, the SDK now accepts `tokenTools` and/or `gatewayUrl` directly in the Factory constructor. This type will be removed in a v2 release */ tokenMap?: TokenMap; tokenTools?: TokenTools; }; +export type SolanaWalletAdapter = { + publicKey: { + toBuffer: () => Buffer; + }; + signMessage: (message: Uint8Array) => Promise; +}; + +export type WalletAdapter = SolanaWalletAdapter | EthereumWalletAdapter; + +export type EthereumWalletSigner = Pick< + JsonRpcSigner, + 'signMessage' | 'sendTransaction' +>; + +export type EthereumWalletAdapter = { + getSigner: () => EthereumWalletSigner; +}; + +export function isSolanaWalletAdapter( + walletAdapter: SolanaWalletAdapter | EthereumWalletAdapter, +): walletAdapter is SolanaWalletAdapter { + return 'publicKey' in walletAdapter && 'signMessage' in walletAdapter; +} + +export type GetTurboSignerParams = { + providedSigner: TurboSigner | undefined; + providedPrivateKey: TurboWallet | undefined; + providedWalletAdapter: WalletAdapter | undefined; + token: TokenType; + logger: TurboWinstonLogger; +}; + +export function isEthereumWalletAdapter( + walletAdapter: SolanaWalletAdapter | EthereumWalletAdapter, +): walletAdapter is EthereumWalletAdapter { + return 'getSigner' in walletAdapter; +} + export type TurboUnauthenticatedClientConfiguration = { paymentService: TurboUnauthenticatedPaymentServiceInterface; uploadService: TurboUnauthenticatedUploadServiceInterface; @@ -457,6 +502,7 @@ export type TurboDataItemSignerParams = { logger?: TurboLogger; signer: TurboSigner; token: TokenType; + walletAdapter?: WalletAdapter; }; export interface TurboDataItemSigner { diff --git a/src/web/factory.ts b/src/web/factory.ts index f54a1098..e3477d5b 100644 --- a/src/web/factory.ts +++ b/src/web/factory.ts @@ -13,42 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EthereumSigner, HexSolanaSigner } from '@dha-team/arbundles'; - import { TurboBaseFactory } from '../common/factory.js'; -import { - TurboAuthenticatedClient, - TurboAuthenticatedPaymentService, - defaultTokenMap, -} from '../common/index.js'; import { TurboDataItemAbstractSigner } from '../common/signer.js'; import { - TokenType, + GetTurboSignerParams, TurboAuthenticatedConfiguration, - TurboSigner, - TurboWallet, + TurboAuthenticatedUploadServiceConfiguration, + TurboAuthenticatedUploadServiceInterface, } from '../types.js'; import { createTurboSigner } from '../utils/common.js'; import { TurboWebArweaveSigner } from './signer.js'; import { TurboAuthenticatedUploadService } from './upload.js'; export class TurboFactory extends TurboBaseFactory { - protected static getSigner( - providedSigner: TurboSigner | undefined, - providedPrivateKey: TurboWallet | undefined, - token: TokenType, - ): TurboDataItemAbstractSigner { + protected getSigner({ + providedPrivateKey, + logger, + providedSigner, + providedWalletAdapter, + token, + }: GetTurboSignerParams): TurboDataItemAbstractSigner { return new TurboWebArweaveSigner({ signer: createTurboSigner({ signer: providedSigner, privateKey: providedPrivateKey, token, }), - logger: this.logger, + logger: logger, token, + walletAdapter: providedWalletAdapter, }); } + protected getAuthenticatedUploadService( + config: TurboAuthenticatedUploadServiceConfiguration, + ): TurboAuthenticatedUploadServiceInterface { + // Import the TurboAuthenticatedUploadService class from the web upload module + return new TurboAuthenticatedUploadService(config); + } + static authenticated({ privateKey, signer: providedSigner, @@ -59,53 +62,16 @@ export class TurboFactory extends TurboBaseFactory { tokenMap, tokenTools, }: TurboAuthenticatedConfiguration) { - token = token === 'pol' ? 'matic' : token; - - if (!token) { - if (providedSigner) { - // Derive token from signer if not provided - if (providedSigner instanceof EthereumSigner) { - token = 'ethereum'; - } else if (providedSigner instanceof HexSolanaSigner) { - token = 'solana'; - } else { - token = 'arweave'; - } - } else { - token = 'arweave'; - } - } - - const turboSigner = this.getSigner(providedSigner, privateKey, token); - - token ??= 'arweave'; // default to arweave if token is not provided - if (!tokenTools) { - if (tokenMap && token === 'arweave') { - tokenTools = tokenMap.arweave; - } - tokenTools = defaultTokenMap[token]?.({ - gatewayUrl, - logger: this.logger, - }); - } - - const paymentService = new TurboAuthenticatedPaymentService({ - ...paymentServiceConfig, - signer: turboSigner, - logger: this.logger, + return new TurboFactory().getAuthenticatedTurbo({ + privateKey, + signer: providedSigner, + paymentServiceConfig, + uploadServiceConfig, token, + gatewayUrl, + tokenMap, tokenTools, - }); - const uploadService = new TurboAuthenticatedUploadService({ - ...uploadServiceConfig, - signer: turboSigner, logger: this.logger, - token, - }); - return new TurboAuthenticatedClient({ - uploadService, - paymentService, - signer: turboSigner, }); } } From 9811b45c45c01e63f6a688fa264785a41648d233 Mon Sep 17 00:00:00 2001 From: Derek Sonnenberg Date: Mon, 30 Sep 2024 11:45:09 -0500 Subject: [PATCH 2/6] refactor: abstract wallet adapter code to private function PE-6052 --- src/common/factory.ts | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/common/factory.ts b/src/common/factory.ts index 5ebeb991..91d46590 100644 --- a/src/common/factory.ts +++ b/src/common/factory.ts @@ -21,13 +21,12 @@ import { } from '@dha-team/arbundles'; import { + GetTurboSignerParams, TokenType, TurboAuthenticatedConfiguration, TurboAuthenticatedUploadServiceConfiguration, TurboAuthenticatedUploadServiceInterface, - TurboSigner, TurboUnauthenticatedConfiguration, - TurboWallet, WalletAdapter, isEthereumWalletAdapter, isSolanaWalletAdapter, @@ -89,13 +88,7 @@ export abstract class TurboBaseFactory { providedWalletAdapter, logger, token, - }: { - providedSigner: TurboSigner | undefined; - providedPrivateKey: TurboWallet | undefined; - providedWalletAdapter: WalletAdapter | undefined; - token: TokenType; - logger: TurboWinstonLogger; - }): TurboDataItemAbstractSigner; + }: GetTurboSignerParams): TurboDataItemAbstractSigner; protected abstract getAuthenticatedUploadService( config: TurboAuthenticatedUploadServiceConfiguration, @@ -129,26 +122,10 @@ export abstract class TurboBaseFactory { token = 'arweave'; } } + token ??= 'arweave'; // default to arweave if token is not provided if (walletAdapter) { - if (token === 'solana') { - if (!isSolanaWalletAdapter(walletAdapter)) { - throw new Error( - 'Solana wallet adapter must implement publicKey and signMessage', - ); - } - providedSigner = new HexInjectedSolanaSigner(walletAdapter); - } - - if (token == 'ethereum') { - if (!isEthereumWalletAdapter(walletAdapter)) { - throw new Error('Ethereum wallet adapter must implement getSigner'); - } - providedSigner = new InjectedEthereumSigner(walletAdapter); - } - throw new Error( - 'Wallet adapter is currently only supported for Solana and Ethereum', - ); + providedSigner = this.signerFromAdapter(walletAdapter, token); } const turboSigner = this.getSigner({ @@ -159,7 +136,6 @@ export abstract class TurboBaseFactory { providedWalletAdapter: walletAdapter, }); - token ??= 'arweave'; // default to arweave if token is not provided if (!tokenTools) { if (tokenMap && token === 'arweave') { tokenTools = tokenMap.arweave; @@ -189,4 +165,28 @@ export abstract class TurboBaseFactory { signer: turboSigner, }); } + + private signerFromAdapter(walletAdapter: WalletAdapter, token: TokenType) { + if (token === 'solana') { + if (!isSolanaWalletAdapter(walletAdapter)) { + throw new Error( + 'Unsupported wallet adapter -- must implement publicKey and signMessage', + ); + } + return new HexInjectedSolanaSigner(walletAdapter); + } + + if (token === 'ethereum') { + if (!isEthereumWalletAdapter(walletAdapter)) { + throw new Error( + 'Unsupported wallet adapter -- must implement getSigner', + ); + } + return new InjectedEthereumSigner(walletAdapter); + } + + throw new Error( + 'Wallet adapter is currently only supported for Solana and Ethereum', + ); + } } From 088457c7f2b581abe62d6ea8b4a349e5cd0090d3 Mon Sep 17 00:00:00 2001 From: Derek Sonnenberg Date: Mon, 30 Sep 2024 12:27:28 -0500 Subject: [PATCH 3/6] test: init coverage on new factory input PE-6052 --- src/common/signer.ts | 1 - src/node/factory.ts | 2 ++ src/web/factory.ts | 2 ++ tests/turbo.web.test.ts | 53 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/common/signer.ts b/src/common/signer.ts index 0998a041..4c50052b 100644 --- a/src/common/signer.ts +++ b/src/common/signer.ts @@ -180,7 +180,6 @@ export abstract class TurboDataItemAbstractSigner return tx.txHash; } - // TODO: ETH Web wallet tx signing/sending const provider = new ethers.JsonRpcProvider(gatewayUrl); const ethWalletAndProvider = new EthereumWallet( keyAsStringFromUint8Array, diff --git a/src/node/factory.ts b/src/node/factory.ts index 275a4a6a..c87c0d9a 100644 --- a/src/node/factory.ts +++ b/src/node/factory.ts @@ -61,6 +61,7 @@ export class TurboFactory extends TurboBaseFactory { tokenMap, gatewayUrl, tokenTools, + walletAdapter, }: TurboAuthenticatedConfiguration) { return new TurboFactory().getAuthenticatedTurbo({ privateKey, @@ -71,6 +72,7 @@ export class TurboFactory extends TurboBaseFactory { tokenMap, gatewayUrl, tokenTools, + walletAdapter, logger: this.logger, }); } diff --git a/src/web/factory.ts b/src/web/factory.ts index e3477d5b..048e2d6c 100644 --- a/src/web/factory.ts +++ b/src/web/factory.ts @@ -61,6 +61,7 @@ export class TurboFactory extends TurboBaseFactory { gatewayUrl, tokenMap, tokenTools, + walletAdapter, }: TurboAuthenticatedConfiguration) { return new TurboFactory().getAuthenticatedTurbo({ privateKey, @@ -72,6 +73,7 @@ export class TurboFactory extends TurboBaseFactory { tokenMap, tokenTools, logger: this.logger, + walletAdapter, }); } } diff --git a/tests/turbo.web.test.ts b/tests/turbo.web.test.ts index 6936fd31..4160268a 100644 --- a/tests/turbo.web.test.ts +++ b/tests/turbo.web.test.ts @@ -8,6 +8,7 @@ import { import { CanceledError } from 'axios'; import { BigNumber } from 'bignumber.js'; import { expect } from 'chai'; +import { TransactionResponse } from 'ethers'; import { File } from 'node-fetch'; import { ReadableStream } from 'node:stream/web'; import { restore, stub } from 'sinon'; @@ -102,6 +103,58 @@ describe('Browser environment', () => { expect(turbo).to.be.instanceOf(TurboAuthenticatedClient); }); + it('should return a TurboAuthenticatedClient when a compatible solana walletAdapter is provided', async () => { + const turbo = TurboFactory.authenticated({ + token: 'solana', + walletAdapter: { + signMessage: (m) => Promise.resolve(m), + publicKey: { toBuffer: () => Buffer.from(testSolWallet, 'hex') }, + }, + }); + expect(turbo).to.be.instanceOf(TurboAuthenticatedClient); + }); + + it('throws an error when an incompatible solana walletAdapter is provided', async () => { + expect(() => + TurboFactory.authenticated({ + token: 'solana', + walletAdapter: { + getSigner: () => ({ + signMessage: (m) => Promise.resolve(m as string), + sendTransaction: () => + Promise.resolve({ hash: 'hash' } as TransactionResponse), + }), + }, + }), + ).to.throw('Unsupported wallet adapter'); + }); + + it('should return a TurboAuthenticatedClient with InjectedEthereumSigner when a compatible ethereum walletAdapter is provided', async () => { + const turbo = TurboFactory.authenticated({ + token: 'ethereum', + walletAdapter: { + getSigner: () => ({ + signMessage: (m) => Promise.resolve(m as string), + sendTransaction: () => + Promise.resolve({ hash: 'hash' } as TransactionResponse), + }), + }, + }); + expect(turbo).to.be.instanceOf(TurboAuthenticatedClient); + }); + + it('throws an error when an incompatible ethereum walletAdapter is provided', async () => { + expect(() => + TurboFactory.authenticated({ + token: 'ethereum', + walletAdapter: { + signMessage: (m) => Promise.resolve(m), + publicKey: { toBuffer: () => Buffer.from(testEthWallet, 'hex') }, + }, + }), + ).to.throw('Unsupported wallet adapter'); + }); + it('should return a TurboAuthenticatedClient when running in Node environment and a provided base58 SOL secret key', async () => { const turbo = TurboFactory.authenticated({ privateKey: testSolWallet, From d92d178e589687fb8653cac375553311c8e1fe04 Mon Sep 17 00:00:00 2001 From: Derek Sonnenberg Date: Mon, 30 Sep 2024 12:36:51 -0500 Subject: [PATCH 4/6] test: improve test coverage PE-6052 --- src/common/factory.ts | 2 +- tests/turbo.web.test.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/common/factory.ts b/src/common/factory.ts index 91d46590..69b14dfd 100644 --- a/src/common/factory.ts +++ b/src/common/factory.ts @@ -186,7 +186,7 @@ export abstract class TurboBaseFactory { } throw new Error( - 'Wallet adapter is currently only supported for Solana and Ethereum', + 'Unsupported wallet adapter -- wallet adapter is currently only supported for Solana and Ethereum', ); } } diff --git a/tests/turbo.web.test.ts b/tests/turbo.web.test.ts index 4160268a..4f1ee080 100644 --- a/tests/turbo.web.test.ts +++ b/tests/turbo.web.test.ts @@ -141,6 +141,13 @@ describe('Browser environment', () => { }, }); expect(turbo).to.be.instanceOf(TurboAuthenticatedClient); + expect( + await turbo.signer.sendTransaction({ + amount: BigNumber('1'), + target: 'target', + gatewayUrl: 'http://this.location', + }), + ).to.equal('hash'); }); it('throws an error when an incompatible ethereum walletAdapter is provided', async () => { @@ -155,6 +162,18 @@ describe('Browser environment', () => { ).to.throw('Unsupported wallet adapter'); }); + it('throws an error when a walletAdapter is provided with an incompatible token', async () => { + expect(() => + TurboFactory.authenticated({ + token: 'arweave', + walletAdapter: { + signMessage: (m) => Promise.resolve(m), + publicKey: { toBuffer: () => Buffer.from(testEthWallet, 'hex') }, + }, + }), + ).to.throw('Unsupported wallet adapter'); + }); + it('should return a TurboAuthenticatedClient when running in Node environment and a provided base58 SOL secret key', async () => { const turbo = TurboFactory.authenticated({ privateKey: testSolWallet, From 426cca8dee1a37a48bf17d45d065989647f195e3 Mon Sep 17 00:00:00 2001 From: Derek Sonnenberg Date: Tue, 1 Oct 2024 12:18:34 -0500 Subject: [PATCH 5/6] refactor: bump SOL polling options PE-6052 --- src/common/token/solana.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/token/solana.ts b/src/common/token/solana.ts index 6dd3e0e2..48f0f4e9 100644 --- a/src/common/token/solana.ts +++ b/src/common/token/solana.ts @@ -50,8 +50,8 @@ export class SolanaToken implements TokenTools { gatewayUrl = 'https://api.mainnet-beta.solana.com', pollingOptions = { maxAttempts: 10, - pollingIntervalMs: 5_000, - initialBackoffMs: 7_000, + pollingIntervalMs: 2_500, + initialBackoffMs: 500, }, }: TokenConfig = {}) { this.logger = logger; From d8b51bb76fda8cb8c446a3aaf8616d99b40e9cf4 Mon Sep 17 00:00:00 2001 From: Derek Sonnenberg Date: Tue, 1 Oct 2024 14:57:53 -0500 Subject: [PATCH 6/6] refactor: use buffer imports where used for web type safety PE-6052 --- src/common/http.ts | 1 + src/node/upload.ts | 1 + src/web/signer.ts | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/http.ts b/src/common/http.ts index da371816..51d51d2d 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -15,6 +15,7 @@ */ import { AxiosInstance } from 'axios'; import { IAxiosRetryConfig } from 'axios-retry'; +import { Buffer } from 'node:buffer'; import { Readable } from 'stream'; import { ReadableStream } from 'stream/web'; diff --git a/src/node/upload.ts b/src/node/upload.ts index 59c3d645..b351893b 100644 --- a/src/node/upload.ts +++ b/src/node/upload.ts @@ -15,6 +15,7 @@ */ import { createReadStream, promises, statSync } from 'fs'; import { lookup } from 'mime-types'; +import { Buffer } from 'node:buffer'; import { Readable } from 'node:stream'; import { join } from 'path'; diff --git a/src/web/signer.ts b/src/web/signer.ts index 78be0852..59700afb 100644 --- a/src/web/signer.ts +++ b/src/web/signer.ts @@ -18,8 +18,10 @@ import { ArweaveSigner, EthereumSigner, HexSolanaSigner, + InjectedEthereumSigner, createData, } from '@dha-team/arbundles'; +import { Buffer } from 'node:buffer'; import { TurboDataItemAbstractSigner } from '../common/signer.js'; import { @@ -47,7 +49,8 @@ export class TurboWebArweaveSigner extends TurboDataItemAbstractSigner { // for arconnect, we need to make sure we have the public key before create data if ( this.signer.publicKey === undefined && - this.signer instanceof ArconnectSigner + (this.signer instanceof ArconnectSigner || + this.signer instanceof InjectedEthereumSigner) ) { await this.signer.setPublicKey(); }