Skip to content

Commit

Permalink
Merge pull request #188 from ardriveapp/PE-6052-SOL-ETH-web-signing
Browse files Browse the repository at this point in the history
PE-6052 -- feat(web): implement walletAdapter for SOL and ETH web signing support
  • Loading branch information
fedellen authored Oct 2, 2024
2 parents 26d8225 + d8b51bb commit bb280ac
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 126 deletions.
141 changes: 138 additions & 3 deletions src/common/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,35 @@
* 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 {
GetTurboSignerParams,
TokenType,
TurboAuthenticatedConfiguration,
TurboAuthenticatedUploadServiceConfiguration,
TurboAuthenticatedUploadServiceInterface,
TurboUnauthenticatedConfiguration,
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 {
Expand All @@ -39,6 +64,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,
Expand All @@ -54,4 +81,112 @@ export abstract class TurboBaseFactory {
paymentService,
});
}

protected abstract getSigner({
providedPrivateKey,
providedSigner,
providedWalletAdapter,
logger,
token,
}: GetTurboSignerParams): 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';
}
}
token ??= 'arweave'; // default to arweave if token is not provided

if (walletAdapter) {
providedSigner = this.signerFromAdapter(walletAdapter, token);
}

const turboSigner = this.getSigner({
providedSigner,
providedPrivateKey: privateKey,
token,
logger,
providedWalletAdapter: walletAdapter,
});

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

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(
'Unsupported wallet adapter -- wallet adapter is currently only supported for Solana and Ethereum',
);
}
}
1 change: 1 addition & 0 deletions src/common/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
26 changes: 25 additions & 1 deletion src/common/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
TurboLogger,
TurboSignedDataItemFactory,
TurboSigner,
WalletAdapter,
isEthereumWalletAdapter,
} from '../types.js';
import {
fromB64Url,
Expand All @@ -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 {
Expand Down Expand Up @@ -125,6 +130,26 @@ export abstract class TurboDataItemAbstractSigner
amount,
gatewayUrl,
}: SendTxWithSignerParams): Promise<string> {
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!',
Expand Down Expand Up @@ -155,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,
Expand Down
4 changes: 2 additions & 2 deletions src/common/token/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
86 changes: 28 additions & 58 deletions src/node/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -56,54 +61,19 @@ export class TurboFactory extends TurboBaseFactory {
tokenMap,
gatewayUrl,
tokenTools,
walletAdapter,
}: 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,
walletAdapter,
logger: this.logger,
token,
});
return new TurboAuthenticatedClient({
uploadService,
paymentService,
signer: turboSigner,
});
}
}
1 change: 1 addition & 0 deletions src/node/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Loading

0 comments on commit bb280ac

Please sign in to comment.