feat: Add optional getAddress method to Signature
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
|
||||
# Optional eslint cache
|
||||
@@ -26,3 +27,4 @@ yalc.lock
|
||||
bundles/
|
||||
|
||||
.secrets
|
||||
logs
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Signature } from '../../contract/Signature';
|
||||
import { CustomSignature, Signature } from '../../contract/Signature';
|
||||
import { defaultCacheOptions, WarpFactory } from '../../core/WarpFactory';
|
||||
|
||||
describe('Wallet', () => {
|
||||
@@ -152,4 +152,64 @@ describe('Wallet', () => {
|
||||
expect(sut.signer).toEqual(sampleFunction);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAddress', () => {
|
||||
|
||||
it('should getAddress for ArWallet signer', async () => {
|
||||
const warp = WarpFactory.forMainnet();
|
||||
const arWallet = await warp.generateWallet();
|
||||
|
||||
const signature = new Signature(warp, arWallet.jwk);
|
||||
|
||||
const address = await signature.getAddress();
|
||||
expect(address).toStrictEqual(arWallet.address);
|
||||
});
|
||||
|
||||
it('should call getAddress for customSignature, if getAddress provided', async () => {
|
||||
const warp = WarpFactory.forMainnet();
|
||||
const customSignature: CustomSignature = {
|
||||
type: 'ethereum',
|
||||
signer: sampleFunction,
|
||||
getAddress: () => Promise.resolve("owner")
|
||||
}
|
||||
|
||||
const signature = new Signature(warp, customSignature);
|
||||
|
||||
const address = await signature.getAddress();
|
||||
expect(address).toStrictEqual("owner");
|
||||
});
|
||||
|
||||
it('should call getAddress for customSignature, if getAddress NOT provided', async () => {
|
||||
const warp = WarpFactory.forMainnet();
|
||||
const customSignature: CustomSignature = {
|
||||
type: 'ethereum',
|
||||
signer: async (tx) => { tx.owner = "owner" },
|
||||
}
|
||||
|
||||
const signature = new Signature(warp, customSignature);
|
||||
|
||||
const address = await signature.getAddress();
|
||||
expect(address).toStrictEqual("owner");
|
||||
});
|
||||
|
||||
it('should use cached valued from getAddress', async () => {
|
||||
const warp = WarpFactory.forMainnet();
|
||||
const mockedSigner = jest.fn(async (tx) => { tx.owner = "owner" });
|
||||
const customSignature: CustomSignature = {
|
||||
type: 'ethereum',
|
||||
signer: mockedSigner,
|
||||
}
|
||||
|
||||
const signature = new Signature(warp, customSignature);
|
||||
|
||||
const address = await signature.getAddress();
|
||||
expect(address).toStrictEqual("owner");
|
||||
|
||||
const cachedAddress = await signature.getAddress();
|
||||
expect(cachedAddress).toStrictEqual("owner");
|
||||
|
||||
expect(mockedSigner).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -668,15 +668,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
if (caller) {
|
||||
effectiveCaller = caller;
|
||||
} else if (this.signature) {
|
||||
// we're creating this transaction just to call the signing function on it
|
||||
// - and retrieve the caller/owner
|
||||
const dummyTx = await arweave.createTransaction({
|
||||
data: Math.random().toString().slice(-4),
|
||||
reward: '72600854',
|
||||
last_tx: 'p7vc1iSP6bvH_fCeUFa9LqoV5qiyW-jdEKouAT0XMoSwrNraB9mgpi29Q10waEpO'
|
||||
});
|
||||
await this.signature.signer(dummyTx);
|
||||
effectiveCaller = await arweave.wallets.ownerToAddress(dummyTx.owner);
|
||||
effectiveCaller = await this.signature.getAddress();
|
||||
} else {
|
||||
effectiveCaller = '';
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Warp } from '../core/Warp';
|
||||
import { ArWallet } from './deploy/CreateContract';
|
||||
import { Transaction } from '../utils/types/arweave-types';
|
||||
import { Signer } from './deploy/DataItem';
|
||||
import { BundlerSigner } from './deploy/DataItem';
|
||||
|
||||
export type SignatureType = 'arweave' | 'ethereum';
|
||||
export type SigningFunction = (tx: Transaction) => Promise<void>;
|
||||
export type CustomSignature = { signer: SigningFunction; type: SignatureType };
|
||||
export type CustomSignature = {
|
||||
signer: SigningFunction;
|
||||
type: SignatureType;
|
||||
getAddress?: () => Promise<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
Different types which can be used to sign transaction or data item
|
||||
@@ -15,22 +19,79 @@ Different types which can be used to sign transaction or data item
|
||||
- Signer - arbundles specific class which allows to sign data items (only this type can be used when bundling is enabled and data items
|
||||
are being created)
|
||||
*/
|
||||
export type SignatureProvider = ArWallet | CustomSignature | Signer;
|
||||
export type SignatureProvider = ArWallet | CustomSignature | BundlerSigner;
|
||||
|
||||
export class Signature {
|
||||
signer: SigningFunction;
|
||||
type: SignatureType;
|
||||
readonly type: SignatureType;
|
||||
readonly warp: Warp;
|
||||
private readonly signatureProviderType: 'CustomSignature' | 'ArWallet' | 'BundlerSigner';
|
||||
private readonly wallet;
|
||||
private cachedAddress?: string;
|
||||
|
||||
constructor(warp: Warp, walletOrSignature: ArWallet | CustomSignature) {
|
||||
constructor(warp: Warp, walletOrSignature: SignatureProvider) {
|
||||
this.warp = warp;
|
||||
|
||||
if (this.isCustomSignature(walletOrSignature)) {
|
||||
this.assertEnvForCustomSigner(walletOrSignature);
|
||||
this.assertEnvForCustomSigner(walletOrSignature.type);
|
||||
this.signer = walletOrSignature.signer;
|
||||
this.type = walletOrSignature.type;
|
||||
this.signatureProviderType = 'CustomSignature';
|
||||
} else if (this.isValidBundlerSignature(walletOrSignature)) {
|
||||
this.signatureProviderType = 'BundlerSigner';
|
||||
this.type = decodeBundleSignatureType(walletOrSignature.signatureType);
|
||||
} else {
|
||||
this.assignDefaultSigner(walletOrSignature);
|
||||
this.assignArweaveSigner(walletOrSignature);
|
||||
this.signatureProviderType = 'ArWallet';
|
||||
this.type = 'arweave';
|
||||
}
|
||||
this.wallet = walletOrSignature;
|
||||
}
|
||||
|
||||
async getAddress(): Promise<string> {
|
||||
if (this.cachedAddress) {
|
||||
return this.cachedAddress;
|
||||
}
|
||||
|
||||
switch (this.signatureProviderType) {
|
||||
case 'CustomSignature': {
|
||||
if (this.wallet.getAddress) {
|
||||
this.cachedAddress = await this.wallet.getAddress();
|
||||
} else {
|
||||
this.cachedAddress = await this.deduceSignerBySigning();
|
||||
}
|
||||
return this.cachedAddress;
|
||||
}
|
||||
case 'ArWallet': {
|
||||
this.cachedAddress = await this.deduceSignerBySigning();
|
||||
return this.cachedAddress;
|
||||
}
|
||||
case 'BundlerSigner': {
|
||||
// If we can parse publicKey to `signatureType` address, we don't have to call it
|
||||
this.cachedAddress = await this.deduceSignerBySigning();
|
||||
return this.cachedAddress;
|
||||
}
|
||||
default:
|
||||
throw Error('Unknown Signature::signatureProvider : ' + this.signatureProviderType);
|
||||
}
|
||||
}
|
||||
|
||||
private async deduceSignerBySigning() {
|
||||
const { arweave } = this.warp;
|
||||
|
||||
const dummyTx = await arweave.createTransaction({
|
||||
data: Math.random().toString().slice(-4),
|
||||
reward: '72600854',
|
||||
last_tx: 'p7vc1iSP6bvH_fCeUFa9LqoV5qiyW-jdEKouAT0XMoSwrNraB9mgpi29Q10waEpO'
|
||||
});
|
||||
await this.signer(dummyTx);
|
||||
|
||||
if (this.type === 'ethereum') {
|
||||
return dummyTx.owner;
|
||||
} else if (this.type === 'arweave') {
|
||||
return arweave.wallets.ownerToAddress(dummyTx.owner);
|
||||
} else {
|
||||
throw Error('Unknown Signature::type');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,25 +101,51 @@ export class Signature {
|
||||
}
|
||||
}
|
||||
|
||||
private assignDefaultSigner(walletOrSignature) {
|
||||
private assignArweaveSigner(walletOrSignature) {
|
||||
this.signer = async (tx: Transaction) => {
|
||||
await this.warp.arweave.transactions.sign(tx, walletOrSignature);
|
||||
};
|
||||
this.type = 'arweave';
|
||||
}
|
||||
|
||||
private assertEnvForCustomSigner(walletOrSignature: CustomSignature) {
|
||||
private assertEnvForCustomSigner(signatureType: SignatureType) {
|
||||
if (
|
||||
walletOrSignature.type !== 'arweave' &&
|
||||
signatureType !== 'arweave' &&
|
||||
(!(this.warp.environment == 'mainnet') || !(this.warp.interactionsLoader.type() == 'warp'))
|
||||
) {
|
||||
throw new Error(
|
||||
`Unable to use signing function of type: ${walletOrSignature.type} when not in mainnet environment or bundling is disabled.`
|
||||
`Unable to use signing function of type: ${signatureType} when not in mainnet environment or bundling is disabled.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private isCustomSignature(signature: ArWallet | CustomSignature): signature is CustomSignature {
|
||||
private isCustomSignature(signature: SignatureProvider): signature is CustomSignature {
|
||||
return (signature as CustomSignature).signer !== undefined;
|
||||
}
|
||||
|
||||
private isValidBundlerSignature(signature: SignatureProvider): signature is BundlerSigner {
|
||||
const bundlerSignature = signature as BundlerSigner;
|
||||
|
||||
// "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck"
|
||||
const isBundlerSignature =
|
||||
!!bundlerSignature.signatureType && !!bundlerSignature.ownerLength && !!bundlerSignature.signatureLength;
|
||||
|
||||
if (isBundlerSignature && !bundlerSignature.publicKey) {
|
||||
throw new Error(
|
||||
`It seems that you are using BundlerSigner, but publicKey is not set! Maybe try calling await bundlerSigner.setPublicKey() before using it.`
|
||||
);
|
||||
}
|
||||
|
||||
return isBundlerSignature;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeBundleSignatureType(bundlerSignatureType: BundlerSigner['signatureType']): SignatureType {
|
||||
// enum: https://github.com/Bundlr-Network/arbundles/blob/9fafdbfec6fbfcbcb538b92ae9bd0d9fbe413fb8/src/constants.ts#L1
|
||||
if (bundlerSignatureType === 3) {
|
||||
return 'ethereum';
|
||||
} else if (bundlerSignatureType === 1) {
|
||||
return 'arweave';
|
||||
} else {
|
||||
throw Error(`Not supported arbundle SignatureType : ${bundlerSignatureType}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { JWKInterface } from 'arweave/node/lib/wallet';
|
||||
import { WarpPluginType } from '../../core/WarpPlugin';
|
||||
import { EvaluationOptions } from '../../core/modules/StateEvaluator';
|
||||
import { Source } from './Source';
|
||||
import { Signer } from './DataItem';
|
||||
import { BundlerSigner } from './DataItem';
|
||||
import { CustomSignature } from 'contract/Signature';
|
||||
|
||||
export type Tags = { name: string; value: string }[];
|
||||
@@ -30,7 +30,7 @@ export const BUNDLR_NODES = ['node1', 'node2'] as const;
|
||||
export type BundlrNodeType = (typeof BUNDLR_NODES)[number];
|
||||
|
||||
export interface CommonContractData {
|
||||
wallet: ArWallet | CustomSignature | Signer;
|
||||
wallet: ArWallet | CustomSignature | BundlerSigner;
|
||||
initState: string;
|
||||
tags?: Tags;
|
||||
transfer?: ArTransfer;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable */
|
||||
export declare abstract class Signer {
|
||||
export declare abstract class BundlerSigner {
|
||||
readonly publicKey: Buffer;
|
||||
readonly signatureType: number;
|
||||
readonly signatureLength: number;
|
||||
@@ -28,7 +28,7 @@ export abstract class DataItem {
|
||||
readonly tags: ResolvesTo<{ name: string; value: string }[]>;
|
||||
readonly rawData: ResolvesTo<Buffer>;
|
||||
readonly data: ResolvesTo<string>;
|
||||
abstract sign(signer: Signer): Promise<Buffer>;
|
||||
abstract sign(signer: BundlerSigner): Promise<Buffer>;
|
||||
abstract isValid(): Promise<boolean>;
|
||||
static async verify(..._: any[]): Promise<boolean> {
|
||||
throw new Error('You must implement `verify`');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ArWallet } from './CreateContract';
|
||||
import { CustomSignature } from '../../contract/Signature';
|
||||
import { Transaction } from '../../utils/types/arweave-types';
|
||||
import { Signer, DataItem } from './DataItem';
|
||||
import { BundlerSigner, DataItem } from './DataItem';
|
||||
|
||||
export interface SourceData {
|
||||
src: string | Buffer;
|
||||
@@ -23,7 +23,7 @@ export interface Source {
|
||||
*/
|
||||
createSource(
|
||||
sourceData: SourceData,
|
||||
wallet: ArWallet | CustomSignature | Signer,
|
||||
wallet: ArWallet | CustomSignature | BundlerSigner,
|
||||
disableBundling?: boolean
|
||||
): Promise<DataItem | Transaction>;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import { Transaction } from '../utils/types/arweave-types';
|
||||
import { DEFAULT_LEVEL_DB_LOCATION } from './WarpFactory';
|
||||
import { LevelDbCache } from '../cache/impl/LevelDbCache';
|
||||
import { SourceData } from '../contract/deploy/Source';
|
||||
import { Signer, DataItem } from '../contract/deploy/DataItem';
|
||||
import { BundlerSigner, DataItem } from '../contract/deploy/DataItem';
|
||||
|
||||
export type WarpEnvironment = 'local' | 'testnet' | 'mainnet' | 'custom';
|
||||
export type KVStorageFactory = (contractTxId: string) => SortKeyCache<unknown>;
|
||||
@@ -118,7 +118,7 @@ export class Warp {
|
||||
|
||||
async createSource(
|
||||
sourceData: SourceData,
|
||||
wallet: ArWallet | CustomSignature | Signer
|
||||
wallet: ArWallet | CustomSignature | BundlerSigner
|
||||
): Promise<Transaction | DataItem> {
|
||||
return await this.createContract.createSource(sourceData, wallet);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user