feat: Add optional getAddress method to Signature
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
# Dependency directories
|
# Dependency directories
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Optional eslint cache
|
# Optional eslint cache
|
||||||
@@ -26,3 +27,4 @@ yalc.lock
|
|||||||
bundles/
|
bundles/
|
||||||
|
|
||||||
.secrets
|
.secrets
|
||||||
|
logs
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Signature } from '../../contract/Signature';
|
import { CustomSignature, Signature } from '../../contract/Signature';
|
||||||
import { defaultCacheOptions, WarpFactory } from '../../core/WarpFactory';
|
import { defaultCacheOptions, WarpFactory } from '../../core/WarpFactory';
|
||||||
|
|
||||||
describe('Wallet', () => {
|
describe('Wallet', () => {
|
||||||
@@ -152,4 +152,64 @@ describe('Wallet', () => {
|
|||||||
expect(sut.signer).toEqual(sampleFunction);
|
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) {
|
if (caller) {
|
||||||
effectiveCaller = caller;
|
effectiveCaller = caller;
|
||||||
} else if (this.signature) {
|
} else if (this.signature) {
|
||||||
// we're creating this transaction just to call the signing function on it
|
effectiveCaller = await this.signature.getAddress();
|
||||||
// - 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);
|
|
||||||
} else {
|
} else {
|
||||||
effectiveCaller = '';
|
effectiveCaller = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Warp } from '../core/Warp';
|
import { Warp } from '../core/Warp';
|
||||||
import { ArWallet } from './deploy/CreateContract';
|
import { ArWallet } from './deploy/CreateContract';
|
||||||
import { Transaction } from '../utils/types/arweave-types';
|
import { Transaction } from '../utils/types/arweave-types';
|
||||||
import { Signer } from './deploy/DataItem';
|
import { BundlerSigner } from './deploy/DataItem';
|
||||||
|
|
||||||
export type SignatureType = 'arweave' | 'ethereum';
|
export type SignatureType = 'arweave' | 'ethereum';
|
||||||
export type SigningFunction = (tx: Transaction) => Promise<void>;
|
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
|
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
|
- 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)
|
are being created)
|
||||||
*/
|
*/
|
||||||
export type SignatureProvider = ArWallet | CustomSignature | Signer;
|
export type SignatureProvider = ArWallet | CustomSignature | BundlerSigner;
|
||||||
|
|
||||||
export class Signature {
|
export class Signature {
|
||||||
signer: SigningFunction;
|
signer: SigningFunction;
|
||||||
type: SignatureType;
|
readonly type: SignatureType;
|
||||||
readonly warp: Warp;
|
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;
|
this.warp = warp;
|
||||||
|
|
||||||
if (this.isCustomSignature(walletOrSignature)) {
|
if (this.isCustomSignature(walletOrSignature)) {
|
||||||
this.assertEnvForCustomSigner(walletOrSignature);
|
this.assertEnvForCustomSigner(walletOrSignature.type);
|
||||||
this.signer = walletOrSignature.signer;
|
this.signer = walletOrSignature.signer;
|
||||||
this.type = walletOrSignature.type;
|
this.type = walletOrSignature.type;
|
||||||
|
this.signatureProviderType = 'CustomSignature';
|
||||||
|
} else if (this.isValidBundlerSignature(walletOrSignature)) {
|
||||||
|
this.signatureProviderType = 'BundlerSigner';
|
||||||
|
this.type = decodeBundleSignatureType(walletOrSignature.signatureType);
|
||||||
} else {
|
} 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) => {
|
this.signer = async (tx: Transaction) => {
|
||||||
await this.warp.arweave.transactions.sign(tx, walletOrSignature);
|
await this.warp.arweave.transactions.sign(tx, walletOrSignature);
|
||||||
};
|
};
|
||||||
this.type = 'arweave';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertEnvForCustomSigner(walletOrSignature: CustomSignature) {
|
private assertEnvForCustomSigner(signatureType: SignatureType) {
|
||||||
if (
|
if (
|
||||||
walletOrSignature.type !== 'arweave' &&
|
signatureType !== 'arweave' &&
|
||||||
(!(this.warp.environment == 'mainnet') || !(this.warp.interactionsLoader.type() == 'warp'))
|
(!(this.warp.environment == 'mainnet') || !(this.warp.interactionsLoader.type() == 'warp'))
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
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;
|
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 { WarpPluginType } from '../../core/WarpPlugin';
|
||||||
import { EvaluationOptions } from '../../core/modules/StateEvaluator';
|
import { EvaluationOptions } from '../../core/modules/StateEvaluator';
|
||||||
import { Source } from './Source';
|
import { Source } from './Source';
|
||||||
import { Signer } from './DataItem';
|
import { BundlerSigner } from './DataItem';
|
||||||
import { CustomSignature } from 'contract/Signature';
|
import { CustomSignature } from 'contract/Signature';
|
||||||
|
|
||||||
export type Tags = { name: string; value: string }[];
|
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 type BundlrNodeType = (typeof BUNDLR_NODES)[number];
|
||||||
|
|
||||||
export interface CommonContractData {
|
export interface CommonContractData {
|
||||||
wallet: ArWallet | CustomSignature | Signer;
|
wallet: ArWallet | CustomSignature | BundlerSigner;
|
||||||
initState: string;
|
initState: string;
|
||||||
tags?: Tags;
|
tags?: Tags;
|
||||||
transfer?: ArTransfer;
|
transfer?: ArTransfer;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export declare abstract class Signer {
|
export declare abstract class BundlerSigner {
|
||||||
readonly publicKey: Buffer;
|
readonly publicKey: Buffer;
|
||||||
readonly signatureType: number;
|
readonly signatureType: number;
|
||||||
readonly signatureLength: number;
|
readonly signatureLength: number;
|
||||||
@@ -28,7 +28,7 @@ export abstract class DataItem {
|
|||||||
readonly tags: ResolvesTo<{ name: string; value: string }[]>;
|
readonly tags: ResolvesTo<{ name: string; value: string }[]>;
|
||||||
readonly rawData: ResolvesTo<Buffer>;
|
readonly rawData: ResolvesTo<Buffer>;
|
||||||
readonly data: ResolvesTo<string>;
|
readonly data: ResolvesTo<string>;
|
||||||
abstract sign(signer: Signer): Promise<Buffer>;
|
abstract sign(signer: BundlerSigner): Promise<Buffer>;
|
||||||
abstract isValid(): Promise<boolean>;
|
abstract isValid(): Promise<boolean>;
|
||||||
static async verify(..._: any[]): Promise<boolean> {
|
static async verify(..._: any[]): Promise<boolean> {
|
||||||
throw new Error('You must implement `verify`');
|
throw new Error('You must implement `verify`');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ArWallet } from './CreateContract';
|
import { ArWallet } from './CreateContract';
|
||||||
import { CustomSignature } from '../../contract/Signature';
|
import { CustomSignature } from '../../contract/Signature';
|
||||||
import { Transaction } from '../../utils/types/arweave-types';
|
import { Transaction } from '../../utils/types/arweave-types';
|
||||||
import { Signer, DataItem } from './DataItem';
|
import { BundlerSigner, DataItem } from './DataItem';
|
||||||
|
|
||||||
export interface SourceData {
|
export interface SourceData {
|
||||||
src: string | Buffer;
|
src: string | Buffer;
|
||||||
@@ -23,7 +23,7 @@ export interface Source {
|
|||||||
*/
|
*/
|
||||||
createSource(
|
createSource(
|
||||||
sourceData: SourceData,
|
sourceData: SourceData,
|
||||||
wallet: ArWallet | CustomSignature | Signer,
|
wallet: ArWallet | CustomSignature | BundlerSigner,
|
||||||
disableBundling?: boolean
|
disableBundling?: boolean
|
||||||
): Promise<DataItem | Transaction>;
|
): Promise<DataItem | Transaction>;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { Transaction } from '../utils/types/arweave-types';
|
|||||||
import { DEFAULT_LEVEL_DB_LOCATION } from './WarpFactory';
|
import { DEFAULT_LEVEL_DB_LOCATION } from './WarpFactory';
|
||||||
import { LevelDbCache } from '../cache/impl/LevelDbCache';
|
import { LevelDbCache } from '../cache/impl/LevelDbCache';
|
||||||
import { SourceData } from '../contract/deploy/Source';
|
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 WarpEnvironment = 'local' | 'testnet' | 'mainnet' | 'custom';
|
||||||
export type KVStorageFactory = (contractTxId: string) => SortKeyCache<unknown>;
|
export type KVStorageFactory = (contractTxId: string) => SortKeyCache<unknown>;
|
||||||
@@ -118,7 +118,7 @@ export class Warp {
|
|||||||
|
|
||||||
async createSource(
|
async createSource(
|
||||||
sourceData: SourceData,
|
sourceData: SourceData,
|
||||||
wallet: ArWallet | CustomSignature | Signer
|
wallet: ArWallet | CustomSignature | BundlerSigner
|
||||||
): Promise<Transaction | DataItem> {
|
): Promise<Transaction | DataItem> {
|
||||||
return await this.createContract.createSource(sourceData, wallet);
|
return await this.createContract.createSource(sourceData, wallet);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user