-
Notifications
You must be signed in to change notification settings - Fork 13
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
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #808 +/- ##
==========================================
+ Coverage 80.68% 80.75% +0.07%
==========================================
Files 85 91 +6
Lines 6549 7145 +596
Branches 1419 1489 +70
==========================================
+ Hits 5284 5770 +486
- Misses 1252 1362 +110
Partials 13 13 ☔ View full report in Codecov by Sentry. |
5bd68ba
to
d0db21b
Compare
d0db21b
to
a342ab7
Compare
@@ -2781,7 +2782,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`); |
There was a problem hiding this comment.
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.
this.template = []; | ||
} | ||
|
||
static from(instructions: z.input<typeof TransactionTemplate>): TransactionTemplateBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obs: z.input<T>
means that this accepts the "unparsed" instruction.
When the type is z.infer<T>
it means the output of the parsed data.
export const SetVarGetWalletAddressOpts = z.object({ | ||
method: z.literal('get_wallet_address'), | ||
index: z.number().optional(), | ||
}); | ||
|
||
export const SetVarGetWalletBalanceOpts = z.object({ | ||
method: z.literal('get_wallet_balance'), | ||
token: TemplateRef.or(TokenSchema.default('00')), | ||
authority: z.enum(['mint', 'melt']).optional(), | ||
}); | ||
|
||
export const SetVarCallArgs = z.discriminatedUnion('method', [ | ||
SetVarGetWalletAddressOpts, | ||
SetVarGetWalletBalanceOpts, | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was changed from the design because non discriminated unions with a lot of optionals were being difficult to parse correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explicit what was changed to facilitate a posterior update of the design?
.addUtxoSelect({ fill: 1 }) | ||
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Should we enforce these parameters to be bigint
s?
.addUtxoSelect({ fill: 1 }) | |
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true }) | |
.addUtxoSelect({ fill: 1n }) | |
.addTokenOutput({ address: '{addr}', amount: 100n, useCreatedToken: true }) |
|
||
outputs: Output[]; | ||
|
||
tokens: string[]; // use token data? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: What were your ideas for this comment?
At first glance it seems to me that it's better to keep a separate array to manage the context that does not involve the actual Transaction object.
tokenSymbol?: string; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
vars: Record<string, any>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion(non-blocking): By using unknown
, each call to the vars need to have its type cast, which helps with the type safety and removes the warning.
vars: Record<string, any>; | |
vars: Record<string, unknown>; |
this.version = CREATE_TOKEN_TX_VERSION; | ||
} | ||
|
||
getTokenDataFor(token: string, useCreatedToken: boolean) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chore: Change the name of the method to better communicate its purpose. As of now, it is a get
method that actually mutates the object and adds a new token.
Also, when useCreatedToken
is passed, token
is discarded, which is also slightly confusing and would be better accompanied by a method docstring to explain it.
return this.tokens.length; | ||
} | ||
|
||
addInput(position: number, ...inputs: Input[]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chore: Change the name to better inform that multiple inputs can be added, and that the expected parameter is actually an array and not an object. Initial suggestions would be addInputs
or addManyInputs
.
ctx.log(`amount(${amount})`); | ||
if (!amount) { | ||
throw new Error('Raw token output missing amount'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion(non-blocking): Move this validation closer to line 263, so that this quick and mandatory validation can fail early.
ctx.addOutput(-1, output); | ||
} | ||
|
||
if (balance.mint_authorities > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought(non-blocking): It feels weird having change addresses for authorities, since each UTXO can only hold a single authority.
I understand this as a user-friendly way of dealing with the transaction having more authority inputs than needed. Not sure if this should have a comment or any kind of feedback/warning for the user.
.addSetVarAction({ name: 'addr', value: address }) | ||
.addSetVarAction({ name: 'token', value: tokenUid }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chore: Not related to these lines, but all tests made here use variable values. At least one of them should test passing the address/value directly to the instructions.
expect(tx.hash).toBeDefined(); | ||
}); | ||
|
||
it('should be able to complete change', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chore: The title is not clear about the difference between this test and the last one.
ctx.addInput(-1, new Input(txId, Number(i))); | ||
arr.push(i); | ||
} | ||
await executor(interpreter, ctx, ins); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion(non-blocking): Add assertions that ensure the steps are the same with a method like contains
, or check the final balance of the transaction to confirm its equivalence to the original one.
Acceptance Criteria
Code organization
The transaction template code is organized in the following modules:
instructions
executor
setvarcommands
action/setvar
instructions.interpreter
context
builder
types
Note
The interpreter for the wallet-service is not part of this implementation.
Note
There are some differences between the design and the instructions on the implementation but this is because of a "not-intuitive-behavior" on zod, specially when dealing with unions.
Future possibilities
In the executors there is some code duplication (especially when creating inputs/outputs and updating the context balance), but I avoided abstracting too much in the initial implementation so we can have more freedom in how we implement each instruction, later I think we should come back to this and make the executor code cleaner.
An issue in the usual transactions of the wallet facade is that if we get a wallet address to use and then a change address it can be the same address since the wallet does not have the context of other addresses that are being used in the transaction.
The template has this information and it could be used to avoid choosing the same address twice in the same tx.
Since this is not an issue with the wallet facade I did not implement in the tx template but we could easily add this.