Skip to content

Commit 185a174

Browse files
Merge pull request #203 from IntersectMBO/fix/koios-asset-list-timeout
fix(provider): tolerate Koios Haskell show string in asset_list and add awaitTx timeout
2 parents c1c6cb1 + d6e2147 commit 185a174

14 files changed

Lines changed: 78 additions & 27 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@evolution-sdk/evolution": patch
3+
---
4+
5+
Fix `awaitTx` failing with a `ParseError` when Koios returns `asset_list` as a Haskell show-formatted string on collateral outputs. Add configurable `timeout` parameter to `awaitTx` across all providers (Koios, Blockfrost, Maestro, Kupmios).

packages/evolution/src/sdk/provider/Blockfrost.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ export class BlockfrostProvider implements Provider {
5454

5555
getDatum = (datumHash: Parameters<Provider["getDatum"]>[0]) => Effect.runPromise(this.Effect.getDatum(datumHash))
5656

57-
awaitTx = (txHash: Parameters<Provider["awaitTx"]>[0], checkInterval?: Parameters<Provider["awaitTx"]>[1]) =>
58-
Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval))
57+
awaitTx = (
58+
txHash: Parameters<Provider["awaitTx"]>[0],
59+
checkInterval?: Parameters<Provider["awaitTx"]>[1],
60+
timeout?: Parameters<Provider["awaitTx"]>[2]
61+
) => Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval, timeout))
5962

6063
submitTx = (cbor: Parameters<Provider["submitTx"]>[0]) => Effect.runPromise(this.Effect.submitTx(cbor))
6164

packages/evolution/src/sdk/provider/Koios.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ export class Koios implements Provider {
5555

5656
getDatum = (datumHash: Parameters<Provider["getDatum"]>[0]) => Effect.runPromise(this.Effect.getDatum(datumHash))
5757

58-
awaitTx = (txHash: Parameters<Provider["awaitTx"]>[0], checkInterval?: Parameters<Provider["awaitTx"]>[1]) =>
59-
Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval))
58+
awaitTx = (
59+
txHash: Parameters<Provider["awaitTx"]>[0],
60+
checkInterval?: Parameters<Provider["awaitTx"]>[1],
61+
timeout?: Parameters<Provider["awaitTx"]>[2]
62+
) => Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval, timeout))
6063

6164
submitTx = (tx: Parameters<Provider["submitTx"]>[0]) => Effect.runPromise(this.Effect.submitTx(tx))
6265

packages/evolution/src/sdk/provider/Kupmios.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,11 @@ export class KupmiosProvider implements Provider {
6767

6868
getDatum = (datumHash: Parameters<Provider["getDatum"]>[0]) => Effect.runPromise(this.Effect.getDatum(datumHash))
6969

70-
awaitTx = (txHash: Parameters<Provider["awaitTx"]>[0], checkInterval?: Parameters<Provider["awaitTx"]>[1]) =>
71-
Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval))
70+
awaitTx = (
71+
txHash: Parameters<Provider["awaitTx"]>[0],
72+
checkInterval?: Parameters<Provider["awaitTx"]>[1],
73+
timeout?: Parameters<Provider["awaitTx"]>[2]
74+
) => Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval, timeout))
7275

7376
evaluateTx = (tx: Parameters<Provider["evaluateTx"]>[0], additionalUTxOs?: Parameters<Provider["evaluateTx"]>[1]) =>
7477
Effect.runPromise(this.Effect.evaluateTx(tx, additionalUTxOs))

packages/evolution/src/sdk/provider/Maestro.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ export class MaestroProvider implements Provider {
5454

5555
getDatum = (datumHash: Parameters<Provider["getDatum"]>[0]) => Effect.runPromise(this.Effect.getDatum(datumHash))
5656

57-
awaitTx = (txHash: Parameters<Provider["awaitTx"]>[0], checkInterval?: Parameters<Provider["awaitTx"]>[1]) =>
58-
Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval))
57+
awaitTx = (
58+
txHash: Parameters<Provider["awaitTx"]>[0],
59+
checkInterval?: Parameters<Provider["awaitTx"]>[1],
60+
timeout?: Parameters<Provider["awaitTx"]>[2]
61+
) => Effect.runPromise(this.Effect.awaitTx(txHash, checkInterval, timeout))
5962

6063
submitTx = (cbor: Parameters<Provider["submitTx"]>[0]) => Effect.runPromise(this.Effect.submitTx(cbor))
6164

packages/evolution/src/sdk/provider/Provider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ export interface ProviderEffect {
118118
*/
119119
readonly awaitTx: (
120120
txHash: TransactionHash.TransactionHash,
121-
checkInterval?: number
121+
checkInterval?: number,
122+
timeout?: number
122123
) => Effect.Effect<boolean, ProviderError>
123124
/**
124125
* Submit a signed transaction to the blockchain.

packages/evolution/src/sdk/provider/internal/BlockfrostEffect.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ export const getDatum = (baseUrl: string, projectId?: string) => (datumHash: Dat
579579
*/
580580
export const awaitTx =
581581
(baseUrl: string, projectId?: string) =>
582-
(txHash: TransactionHash.TransactionHash, checkInterval: number = 5000) => {
582+
(txHash: TransactionHash.TransactionHash, checkInterval: number = 5000, timeout: number = 300_000) => {
583583
const txHashHex = TransactionHash.toHex(txHash)
584584
const checkTx = withRateLimit(
585585
HttpUtils.get(
@@ -592,13 +592,11 @@ export const awaitTx =
592592
)
593593
)
594594

595-
// Poll every checkInterval milliseconds until transaction is found
596-
const pollSchedule = Schedule.fixed(`${checkInterval} millis`).pipe(
597-
Schedule.compose(Schedule.recurs(60)) // Max 60 attempts (5 minutes with 5s interval)
598-
)
599-
600-
return Effect.retry(checkTx, pollSchedule).pipe(
601-
Effect.orElse(() => Effect.succeed(false)) // Return false if not found after max attempts
595+
return Effect.retry(checkTx, Schedule.fixed(`${checkInterval} millis`)).pipe(
596+
Effect.timeout(timeout),
597+
Effect.catchAllCause(
598+
(cause) => new ProviderError({ cause, message: "Failed to await transaction confirmation" })
599+
)
602600
)
603601
}
604602

packages/evolution/src/sdk/provider/internal/Koios.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ export const InputOutputSchema = Schema.Struct({
152152
})
153153
),
154154
reference_script: Schema.NullOr(ReferenceScriptSchema),
155-
asset_list: Schema.Array(AssetSchema)
155+
// Koios can return asset_list as a Haskell show-formatted string on some endpoints (e.g. collateral
156+
// outputs with many assets). Treat any string as null to avoid a parse failure in those cases.
157+
asset_list: Schema.Union(
158+
Schema.NullOr(Schema.Array(AssetSchema)),
159+
Schema.transform(Schema.String, Schema.Null, { decode: () => null, encode: () => "" })
160+
)
156161
})
157162

158163
export interface InputOutput extends Schema.Schema.Type<typeof InputOutputSchema> {}

packages/evolution/src/sdk/provider/internal/KoiosEffect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ export const getDatum = (baseUrl: string, token?: string) => (datumHash: DatumHa
268268

269269
export const awaitTx =
270270
(baseUrl: string, token?: string) =>
271-
(txHash: TransactionHash.TransactionHash, checkInterval = 20000) =>
271+
(txHash: TransactionHash.TransactionHash, checkInterval = 20000, timeout = 160_000) =>
272272
Effect.gen(function* () {
273273
const txHashHex = TransactionHash.toHex(txHash)
274274
const body = {
@@ -284,7 +284,7 @@ export const awaitTx =
284284
schedule: Schedule.exponential(checkInterval),
285285
until: (result) => result.length > 0
286286
}),
287-
Effect.timeout(160_000),
287+
Effect.timeout(timeout),
288288
Effect.catchAllCause(
289289
(cause) => new Provider.ProviderError({ cause, message: "Failed to await transaction confirmation" })
290290
),

packages/evolution/src/sdk/provider/internal/KupmiosEffects.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ export const evaluateTxEffect = (ogmiosUrl: string, headers?: { ogmiosHeader?: R
402402
})
403403

404404
export const awaitTxEffect = (kupoUrl: string, headers?: { kupoHeader?: Record<string, string> }) =>
405-
Effect.fn("awaitTx")(function* (txHash: TransactionHash.TransactionHash, checkInterval = 5000) {
405+
Effect.fn("awaitTx")(function* (txHash: TransactionHash.TransactionHash, checkInterval = 5000, timeout = 160_000) {
406406
const txHashHex = TransactionHash.toHex(txHash)
407407
const pattern = `${kupoUrl}/matches/*@${txHashHex}?unspent`
408408
const schema = Schema.Array(Kupo.UTxOSchema).annotations({
@@ -416,7 +416,7 @@ export const awaitTxEffect = (kupoUrl: string, headers?: { kupoHeader?: Record<s
416416
schedule: Schedule.exponential(checkInterval),
417417
until: (result) => result.length > 0
418418
}),
419-
Effect.timeout(160_000),
419+
Effect.timeout(timeout),
420420
Effect.catchAll((cause) => new Provider.ProviderError({ cause, message: "Failed to await transaction" })),
421421
Effect.as(true)
422422
)

0 commit comments

Comments
 (0)