Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: transaction template #808

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cbb5490
feat: POC implementation
r4mmer Nov 11, 2024
9755aa8
feat: initial implementation
r4mmer Nov 11, 2024
a342ab7
feat: add support for create token transaction
r4mmer Dec 16, 2024
edb3894
feat: add balance management for created token reference
r4mmer Dec 17, 2024
250105d
tests: instruction tests
r4mmer Dec 20, 2024
90dab94
feat: complete instruction
r4mmer Dec 23, 2024
13efa80
feat: complete instruction
r4mmer Dec 23, 2024
fec87f6
feat: executor tests
r4mmer Dec 26, 2024
fff192b
tests: executor and interpreter tests
r4mmer Dec 26, 2024
d46c571
tests(integration): custom token integration template tests
r4mmer Dec 26, 2024
6f91f9e
chore: linter changes
r4mmer Dec 27, 2024
3a3b9ea
chore: linter changes
r4mmer Dec 27, 2024
30f1764
tests(integration): change injectFunds options
r4mmer Dec 27, 2024
3b2ae37
tests(integration): create token tx have different serialization
r4mmer Dec 27, 2024
7099194
tests(integration): include change outputs in checks
r4mmer Dec 27, 2024
edd8234
tests(integration): change output orderr
r4mmer Dec 27, 2024
d120896
tests(integration): add change value to send operation
r4mmer Dec 27, 2024
a50620a
chore: remove helper types to use inferred types from zod
r4mmer Dec 27, 2024
ee5edf4
tests(integration): add change and complete tests
r4mmer Dec 27, 2024
ab0b78e
tests(integration): wrong complete instruction type
r4mmer Dec 30, 2024
49ad6d3
fix: change would add invalid tokens on transaction
r4mmer Dec 31, 2024
674aa2d
tests(unit): mock balance properly
r4mmer Dec 31, 2024
c7e968c
feat: expose transaction template and helper method on the wallet
r4mmer Jan 7, 2025
61b9502
chore: linter changes
r4mmer Jan 8, 2025
ba4110d
test(integration): increase coverage on integration tests
r4mmer Jan 8, 2025
1d8cb3f
tests(integration): change position of tests
r4mmer Jan 8, 2025
b2cd834
tests(integration): change tx version
r4mmer Jan 8, 2025
d82aa19
feat: make tx signing optional when building with the facade
r4mmer Jan 9, 2025
33591c3
feat: correctly pass the arguments to build template
r4mmer Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: POC implementation
  • Loading branch information
r4mmer committed Dec 16, 2024
commit cbb54904be3019dff088e8631bb08cdf7b32aa5e
2 changes: 1 addition & 1 deletion src/new/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -2781,7 +2781,7 @@ class HathorWallet extends EventEmitter {
const tokenIdx = tokenUtils.getTokenIndexFromData(token_data);
const tokenUid = tokens[tokenIdx - 1]?.uid;
if (!tokenUid) {
throw new Error(`Token ${tokenUid} not found in tokens list`);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if !tokenUid means that tokenUid is undefined or empty string, which makes no sense adding to the error message.
This is why I changed to token_data instead, this makes understanding what went wrong easier.

throw new Error(`Invalid token_data ${token_data}, token not found in tokens list`);
}

return {
Expand Down
56 changes: 56 additions & 0 deletions src/template/transaction/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import { RawInputInstruction, TemplateVar, TxTemplateInstruction } from './instructions';

export class TransactionTemplateBuilder {
instructions: TxTemplateInstruction[];

constructor() {
this.instructions = [];
}

static from(instructions: TxTemplateInstruction[]): TransactionTemplateBuilder {
const tt = new TransactionTemplateBuilder();
tt.instructions = instructions;
return tt;
}

addInstruction(ins: TxTemplateInstruction): TransactionTemplateBuilder {
this.instructions.push(ins);
return this;
}

// TODO: other adders

addRawInput(txId: TemplateVar<string>, index: TemplateVar<number>, position: number = -1) {
this.instructions.push({
type: 'input/raw',
position,
txId,
index,
});

return this;
}

addRawOutput(amount: TemplateVar<number>, script: TemplateVar<string>, token: TemplateVar<string>, position: number = -1) {
this.instructions.push({
type: 'output/raw',
position,
amount,
script,
token,
});

return this;
}

export() {
return this.instructions;
}
}
2 changes: 2 additions & 0 deletions src/template/transaction/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './interpreter';
export * from './builder';
192 changes: 192 additions & 0 deletions src/template/transaction/instructions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export type InstructionTypes = 'input/utxo'
| 'input/raw'
| 'output/raw'
| 'output/token'
| 'output/data'
| 'action/shuffle'
| 'action/change'
| 'action/config'
| 'action/setvar';

export const INSTRUCTION_TYPES = [
'input/utxo',
'input/raw',
'output/raw',
'output/token',
'output/data',
'action/shuffle',
'action/change',
'action/config',
'action/setvar',
];

export interface BaseTemplateInstruction {
readonly type: InstructionTypes;
}

export type TemplateVarValue = string|number;
export type TemplateVarName = string;
export type TemplateVarRef = `{${TemplateVarName}}`;
export const TEMPLATE_VAR_REF_RE = /^{(.+)}$/;
export type TemplateVar<T extends TemplateVarValue> = TemplateVarRef|T;

/**
* If the key matches a template reference (i.e. `{name}`) it returns the variable of that name.
* If not the key should be the actual value.
*/
export function getVariable<T extends TemplateVarValue>(ref: TemplateVar<T>, vars: Record<TemplateVarName, TemplateVarValue>): T {
if (typeof ref === 'string') {
const match = ref.match(TEMPLATE_VAR_REF_RE);
if (match !== null) {
const key = match[1];
if (!vars[key]) {
throw new Error(`Variable ${key} not found in available variables`);
}
return vars[key] as T;
}
}

return ref as T;
}

export interface RawInputInstruction extends BaseTemplateInstruction {
readonly type: 'input/raw';
position?: number;
txId: TemplateVar<string>;
index: TemplateVar<number>;
}

export function isRawInputInstruction(x: any): x is RawInputInstruction {
return 'type' in x && x.type === 'input/raw';
}

export interface UtxoSelectInstruction extends BaseTemplateInstruction {
readonly type: 'input/utxo';
position: number;
fill: TemplateVar<number>;
token?: TemplateVar<string>;
authority?: 'mint' | 'melt';
address?: TemplateVar<string>;
autoChange?: boolean;
}

export function isUtxoSelectInstruction(x: any): x is UtxoSelectInstruction {
return 'type' in x && x.type === 'input/utxo';
}

export interface RawOutputInstruction extends BaseTemplateInstruction {
readonly type: 'output/raw';
position: number;
amount?: TemplateVar<number>;
script: TemplateVar<string>; // base64 or hex?
token?: TemplateVar<string>;
timelock?: TemplateVar<number>;
authority?: 'mint' | 'melt',
}

export function isRawOutputInstruction(x: any): x is RawOutputInstruction {
return 'type' in x && x.type === 'output/raw';
}

export interface DataOutputInstruction extends BaseTemplateInstruction {
readonly type: 'output/data',
position: number;
data: TemplateVar<string>;
}

export function isDataOutputInstruction(x: any): x is DataOutputInstruction {
return 'type' in x && x.type === 'output/data';
}

export interface TokenOutputInstruction extends BaseTemplateInstruction {
readonly type: 'output/token';
position: number;
amount: TemplateVar<number>;
token?: TemplateVar<string>;
address?: TemplateVar<string>;
timelock?: TemplateVar<number>;
checkAddress?: boolean;
}

export function isTokenOutputInstruction(x: any): x is TokenOutputInstruction {
return 'type' in x && x.type === 'output/token';
}

export interface ShuffleInstruction extends BaseTemplateInstruction {
readonly type: 'action/shuffle';
target: 'inputs' | 'outputs' | 'all';
}

export function isShuffleInstruction(x: any): x is ShuffleInstruction {
return 'type' in x && x.type === 'action/shuffle';
}

export interface ChangeInstruction extends BaseTemplateInstruction {
readonly type: 'action/change';
token?: TemplateVar<string>;
address?: TemplateVar<string>;
timelock?: TemplateVar<number>;
}

export function isChangeInstruction(x: any): x is ChangeInstruction {
return 'type' in x && x.type === 'action/change';
}

export interface ConfigInstruction extends BaseTemplateInstruction {
readonly type: 'action/config';
version?: TemplateVar<number>;
signalBits?: TemplateVar<number>;
tokenName?: TemplateVar<string>;
tokenSymbol?: TemplateVar<string>;
}

export function isConfigInstruction(x: any): x is ConfigInstruction {
return 'type' in x && x.type === 'action/config';
}

export type SetVarCommand = 'get_wallet_address' | 'get_wallet_balance';

export type SetVarGetWalletAddressOpts = {
unused?: boolean;
withBalance?: number;
withAuthority?: 'mint' | 'melt';
token?: string;
};

export type SetVarGetWalletBalanceOpts = {
token?: string;
};

export type SetVarOptions = SetVarGetWalletAddressOpts | SetVarGetWalletBalanceOpts;

export interface SetVarInstruction extends BaseTemplateInstruction {
readonly type: 'action/setvar';
name: TemplateVarName;
value?: TemplateVarValue;
action?: SetVarCommand;
options?: SetVarOptions;
}

export function isSetVarInstruction(x: any): x is SetVarInstruction {
return 'type' in x && x.type === 'action/setvar';
}

export type TxTemplateInstruction =
| RawInputInstruction
| UtxoSelectInstruction
| RawOutputInstruction
| TokenOutputInstruction
| DataOutputInstruction
| ShuffleInstruction
| ChangeInstruction
| ConfigInstruction
| SetVarInstruction;

export type TransactionTemplate = TxTemplateInstruction[];
Loading