@ardrive/turbo-sdk 2.1.0-alpha.1 → 2.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -126,7 +126,7 @@ class TurboBaseFactory {
126
126
  });
127
127
  }
128
128
  signerFromAdapter(walletAdapter, token) {
129
- if (token === 'solana') {
129
+ if (types_js_1.supportedSolanaSignerTokens.has(token)) {
130
130
  if (!(0, types_js_1.isSolanaWalletAdapter)(walletAdapter)) {
131
131
  throw new Error('Unsupported wallet adapter -- must implement publicKey and signMessage');
132
132
  }
@@ -28,6 +28,34 @@ const logger_js_1 = require("./logger.js");
28
28
  const index_js_1 = require("./token/index.js");
29
29
  exports.developmentPaymentServiceURL = 'https://payment.services.ar-io.dev';
30
30
  exports.defaultPaymentServiceURL = 'https://payment.ardrive.io';
31
+ /**
32
+ * Translate a `/sign` failure into the error a caller can act on, matching
33
+ * the payment service's own response bodies.
34
+ *
35
+ * - 402 → {@link InsufficientCreditsError}, as `createArNSAction` maps it.
36
+ * - 409 → {@link ArNSActionExpiredError}: the chain confirmed the signed
37
+ * bytes can never land. The body says whether the credits came back.
38
+ * - 400 `Action <nonce> expired …` → {@link ArNSActionExpiredError}. Matched
39
+ * on THIS nonce, because the same status carries on-chain program
40
+ * rejections ("Lease has expired", "Record is expired") that are not about
41
+ * the action at all.
42
+ * - Anything else — including 503 "Blockhash not found", which the service
43
+ * returns only when it cannot PROVE expiry — is returned unchanged.
44
+ */
45
+ function arNSSignError(nonce, error) {
46
+ if (!(error instanceof errors_js_1.FailedRequestError))
47
+ return error;
48
+ const { status, message } = error;
49
+ if (status === 402)
50
+ return new errors_js_1.InsufficientCreditsError(message);
51
+ if (status === 409 && /expired/i.test(message)) {
52
+ return new errors_js_1.ArNSActionExpiredError(nonce, status, /credits have been returned/i.test(message), message);
53
+ }
54
+ if (status === 400 && message.includes(`Action ${nonce} expired`)) {
55
+ return new errors_js_1.ArNSActionExpiredError(nonce, status, /expired and was refunded/i.test(message), message);
56
+ }
57
+ return error;
58
+ }
31
59
  class TurboUnauthenticatedPaymentService {
32
60
  constructor({ url = exports.defaultPaymentServiceURL, logger = logger_js_1.Logger.default, retryConfig = (0, http_js_1.defaultRetryConfig)(logger), token = 'arweave', }) {
33
61
  this.logger = logger;
@@ -607,17 +635,34 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
607
635
  * the signature. Replaying a completed action returns `alreadyCompleted:
608
636
  * true` rather than buying twice, so this is safe to call again if a
609
637
  * response is lost.
638
+ *
639
+ * The route is authorised by the ANT owner's signature INSIDE the
640
+ * transaction, not by the payer: the credits were debited when the action
641
+ * was created. So no payer signature is sent by default — for a browser
642
+ * payer that would be a second wallet prompt inside the ~30 s blockhash
643
+ * window. Pass `headers` only for a service that requires them.
644
+ *
645
+ * Failures: {@link ArNSActionExpiredError} when the action or its
646
+ * transaction has provably expired (create a new action);
647
+ * {@link InsufficientCreditsError} on 402. A 503 "Blockhash not found"
648
+ * means expiry could not be proven — re-posting the SAME signed bytes is
649
+ * idempotent and may still succeed; do not re-create the action.
610
650
  */
611
- async signArNSAction(nonce, signedTransaction) {
612
- return this.httpService.post({
613
- endpoint: `/arns/actions/${nonce}/sign`,
614
- headers: {
615
- ...(await this.signer.generateSignedRequestHeaders((0, uuid_js_1.uuidV4)())),
616
- 'content-type': 'application/json',
617
- },
618
- data: Buffer.from(JSON.stringify({ transaction: signedTransaction })),
619
- retry: false,
620
- });
651
+ async signArNSAction(nonce, signedTransaction, headers) {
652
+ try {
653
+ return await this.httpService.post({
654
+ endpoint: `/arns/actions/${nonce}/sign`,
655
+ headers: {
656
+ ...(headers ?? {}),
657
+ 'content-type': 'application/json',
658
+ },
659
+ data: Buffer.from(JSON.stringify({ transaction: signedTransaction })),
660
+ retry: false,
661
+ });
662
+ }
663
+ catch (error) {
664
+ throw arNSSignError(nonce, error);
665
+ }
621
666
  }
622
667
  /**
623
668
  * Status of an action by nonce. Open — no signature required — so it works
@@ -644,7 +689,7 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
644
689
  : undefined);
645
690
  // Fires before any wallet prompt: the action is already debited, so the
646
691
  // caller needs the nonce persisted even if the user walks away here.
647
- await opts.onNonce?.(created.nonce);
692
+ await opts.onNonce?.(created.nonce, created);
648
693
  if (created.status === 'completed')
649
694
  return created;
650
695
  if (owner === undefined) {
@@ -653,6 +698,8 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
653
698
  `Nonce ${created.nonce} is already debited — poll it rather than re-creating.`);
654
699
  }
655
700
  const signed = await owner.signTransaction(created.transaction);
701
+ // The owner's signature is the only prompt between create and submit:
702
+ // `/sign` needs no payer signature (see `signArNSAction`).
656
703
  return this.signArNSAction(created.nonce, signed);
657
704
  }
658
705
  /**
@@ -23,6 +23,7 @@ const spl_token_1 = require("@solana/spl-token");
23
23
  const web3_js_1 = require("@solana/web3.js");
24
24
  const bignumber_js_1 = require("bignumber.js");
25
25
  const bs58_1 = __importDefault(require("bs58"));
26
+ const types_js_1 = require("../../types.js");
26
27
  const common_js_1 = require("../../utils/common.js");
27
28
  const logger_js_1 = require("../logger.js");
28
29
  const solana_js_1 = require("./solana.js");
@@ -80,11 +81,12 @@ class SplToken {
80
81
  data: Buffer.from('turboCreditDestinationAddress=' + turboCreditDestinationAddress),
81
82
  }));
82
83
  }
83
- const serializedTx = tx.serializeMessage();
84
- const signature = await signer.signData(Uint8Array.from(serializedTx));
85
- tx.addSignature(ownerPublicKey, Buffer.from(signature));
84
+ const { signedTx, signature } = signer.walletAdapter !== undefined &&
85
+ (0, types_js_1.isSolanaWalletAdapter)(signer.walletAdapter)
86
+ ? await this.signWithWallet(tx, signer.walletAdapter)
87
+ : await this.signWithKey(tx, ownerPublicKey, signer);
86
88
  const txId = bs58_1.default.encode(signature);
87
- await this.submitTx(tx, txId);
89
+ await this.submitTx(signedTx, txId);
88
90
  this.logger.debug(`Submitted ${this.tokenName} SPL transfer transaction...`, {
89
91
  id: txId,
90
92
  target,
@@ -95,6 +97,32 @@ class SplToken {
95
97
  });
96
98
  return { id: txId, target, reward: '0' };
97
99
  }
100
+ /** Sign the message bytes directly: a private-key signer holds the key. */
101
+ async signWithKey(tx, ownerPublicKey, signer) {
102
+ const signature = await signer.signData(Uint8Array.from(tx.serializeMessage()));
103
+ tx.addSignature(ownerPublicKey, Buffer.from(signature));
104
+ return { signedTx: tx, signature };
105
+ }
106
+ /**
107
+ * Have a browser wallet sign the transaction itself, as the SOL path does.
108
+ *
109
+ * A wallet signs through `signTransaction`, not `signMessage`: it refuses, or
110
+ * cannot display, a serialized transaction handed to it as a message. It may
111
+ * also add instructions of its own before signing (compute budget, guards),
112
+ * so the transaction submitted is the one it returns, never ours with its
113
+ * signature attached. Deserializing drops `lastValidBlockHeight`, so that is
114
+ * carried over for confirmation.
115
+ */
116
+ async signWithWallet(tx, walletAdapter) {
117
+ const signedTx = await walletAdapter.signTransaction(tx);
118
+ const signature = signedTx.signature;
119
+ if (signature === null) {
120
+ throw new Error(`Wallet returned an unsigned ${this.tokenName} transfer transaction`);
121
+ }
122
+ signedTx.recentBlockhash ??= tx.recentBlockhash;
123
+ signedTx.lastValidBlockHeight ??= tx.lastValidBlockHeight;
124
+ return { signedTx, signature: Uint8Array.from(signature) };
125
+ }
98
126
  async submitTx(tx, id) {
99
127
  this.logger.debug(`Submitting ${this.tokenName} fund transaction...`, {
100
128
  id,
@@ -245,9 +245,13 @@ class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
245
245
  createArNSAction(action, params, ownerProof) {
246
246
  return this.paymentService.createArNSAction(action, params, ownerProof);
247
247
  }
248
- /** Submit the owner-signed transaction (FULL serialized tx, base64). */
249
- signArNSAction(nonce, signedTransaction) {
250
- return this.paymentService.signArNSAction(nonce, signedTransaction);
248
+ /**
249
+ * Submit the owner-signed transaction (FULL serialized tx, base64).
250
+ * Authorised by the owner's signature inside the transaction; payer
251
+ * `headers` are optional, for services that require them.
252
+ */
253
+ signArNSAction(nonce, signedTransaction, headers) {
254
+ return this.paymentService.signArNSAction(nonce, signedTransaction, headers);
251
255
  }
252
256
  /** Status by nonce — open, needs no signature. Use it to resume. */
253
257
  getArNSActionStatus(nonce) {
package/lib/cjs/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.arNSFiatPurchaseMethods = exports.arNSActions = exports.arNSPurchaseIntents = exports.validChunkingModes = exports.isJWK = exports.isWebUploadFolderParams = exports.isNodeUploadFolderParams = exports.multipartFinalizedStatus = exports.multipartFailedStatus = exports.multipartPendingStatus = exports.X402Funding = exports.OnDemandFunding = exports.ExistingBalanceFunding = exports.supportedEvmSignerTokens = exports.tokenTypes = exports.fiatCurrencyTypes = void 0;
3
+ exports.arNSFiatPurchaseMethods = exports.arNSActions = exports.arNSPurchaseIntents = exports.validChunkingModes = exports.isJWK = exports.isWebUploadFolderParams = exports.isNodeUploadFolderParams = exports.multipartFinalizedStatus = exports.multipartFailedStatus = exports.multipartPendingStatus = exports.X402Funding = exports.OnDemandFunding = exports.ExistingBalanceFunding = exports.supportedEvmSignerTokens = exports.supportedSolanaSignerTokens = exports.tokenTypes = exports.fiatCurrencyTypes = void 0;
4
4
  exports.isCurrency = isCurrency;
5
5
  exports.isEthPrivateKey = isEthPrivateKey;
6
6
  exports.isSolanaWalletAdapter = isSolanaWalletAdapter;
@@ -35,6 +35,16 @@ exports.tokenTypes = [
35
35
  // USDC (SPL) on Solana — signed with the Solana signer, not an EVM one.
36
36
  'solana-usdc',
37
37
  ];
38
+ /**
39
+ * Tokens paid from a Solana wallet: SOL natively, and ARIO and USDC as SPL
40
+ * transfers signed by the same ed25519 key. A private key for any of these
41
+ * builds a Solana signer, so a Solana wallet adapter must as well.
42
+ */
43
+ exports.supportedSolanaSignerTokens = new Set([
44
+ 'solana',
45
+ 'ario',
46
+ 'solana-usdc',
47
+ ]);
38
48
  exports.supportedEvmSignerTokens = new Set([
39
49
  'ethereum',
40
50
  'base-eth',
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AbortError = exports.FiatPaymentsDisabledError = exports.InsufficientCreditsError = exports.ProvidedInputError = exports.FailedRequestError = exports.UnauthenticatedRequestError = exports.BaseError = void 0;
3
+ exports.AbortError = exports.ArNSActionExpiredError = exports.FiatPaymentsDisabledError = exports.InsufficientCreditsError = exports.ProvidedInputError = exports.FailedRequestError = exports.UnauthenticatedRequestError = exports.BaseError = void 0;
4
4
  /**
5
5
  * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
6
6
  *
@@ -79,6 +79,33 @@ class FiatPaymentsDisabledError extends BaseError {
79
79
  }
80
80
  }
81
81
  exports.FiatPaymentsDisabledError = FiatPaymentsDisabledError;
82
+ /**
83
+ * Raised when an ArNS action's owner-signed transaction reaches `/sign` too
84
+ * late. Solana accepts the transaction for only about 30 seconds after Turbo
85
+ * builds it; signing again cannot help, because that transaction is dead.
86
+ *
87
+ * `creditsReleased` says whether the credits debited at creation are back:
88
+ * - 409: the chain confirmed the bytes can never land. `true` when the
89
+ * service says "Your credits have been returned"; `false` means its
90
+ * immediate refund did not go through and the reconciler will refund.
91
+ * - 400 `Action <nonce> expired …`: `true` for "expired and was refunded";
92
+ * `false` for the variant refused before submission, refunded
93
+ * automatically once the reservation (`expiresAt`, ~15 min) lapses.
94
+ *
95
+ * Extends {@link FailedRequestError}, so existing `status` checks still work.
96
+ * Recovery: create a NEW action if the change is still wanted.
97
+ */
98
+ class ArNSActionExpiredError extends FailedRequestError {
99
+ constructor(nonce, status, creditsReleased,
100
+ /** The service's response body, or a `FailedRequestError` message. */
101
+ message) {
102
+ super(message?.replace(/^Failed request \(Status \d+\): /, '') ??
103
+ `ArNS action ${nonce} expired before its signed transaction was submitted.`, status);
104
+ this.nonce = nonce;
105
+ this.creditsReleased = creditsReleased;
106
+ }
107
+ }
108
+ exports.ArNSActionExpiredError = ArNSActionExpiredError;
82
109
  class AbortError extends BaseError {
83
110
  constructor(message = 'Request was aborted') {
84
111
  super(message);
@@ -17,4 +17,4 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.version = void 0;
19
19
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
20
- exports.version = '2.1.0-alpha.1';
20
+ exports.version = '2.1.0-alpha.3';
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { HexInjectedSolanaSigner, InjectedEthereumSigner, SignatureConfig, } from '@dha-team/arbundles';
17
- import { isEthereumWalletAdapter, isSolanaWalletAdapter, supportedEvmSignerTokens, } from '../types.js';
17
+ import { isEthereumWalletAdapter, isSolanaWalletAdapter, supportedEvmSignerTokens, supportedSolanaSignerTokens, } from '../types.js';
18
18
  import { Logger } from './logger.js';
19
19
  import { TurboAuthenticatedPaymentService, TurboUnauthenticatedPaymentService, } from './payment.js';
20
20
  import { defaultTokenMap } from './token/index.js';
@@ -123,7 +123,7 @@ export class TurboBaseFactory {
123
123
  });
124
124
  }
125
125
  signerFromAdapter(walletAdapter, token) {
126
- if (token === 'solana') {
126
+ if (supportedSolanaSignerTokens.has(token)) {
127
127
  if (!isSolanaWalletAdapter(walletAdapter)) {
128
128
  throw new Error('Unsupported wallet adapter -- must implement publicKey and signMessage');
129
129
  }
@@ -16,7 +16,7 @@
16
16
  import { BigNumber } from 'bignumber.js';
17
17
  import { arNSPurchaseIntents, fiatCurrencyTypes, isCurrency, } from '../types.js';
18
18
  import { isAnyValidUserAddress, isValidArweaveBase64URL, isValidSolanaAddress, } from '../utils/common.js';
19
- import { FailedRequestError, FiatPaymentsDisabledError, InsufficientCreditsError, ProvidedInputError, } from '../utils/errors.js';
19
+ import { ArNSActionExpiredError, FailedRequestError, FiatPaymentsDisabledError, InsufficientCreditsError, ProvidedInputError, } from '../utils/errors.js';
20
20
  import { uuidV4 } from '../utils/uuid.js';
21
21
  import { arNSKeywordsField, arNSMetadataField, arNSOwnerProofHeaders, buildArNSCustodyMessage, } from './arnsActions.js';
22
22
  import { defaultRetryConfig } from './http.js';
@@ -25,6 +25,34 @@ import { Logger } from './logger.js';
25
25
  import { exponentMap, tokenToBaseMap } from './token/index.js';
26
26
  export const developmentPaymentServiceURL = 'https://payment.services.ar-io.dev';
27
27
  export const defaultPaymentServiceURL = 'https://payment.ardrive.io';
28
+ /**
29
+ * Translate a `/sign` failure into the error a caller can act on, matching
30
+ * the payment service's own response bodies.
31
+ *
32
+ * - 402 → {@link InsufficientCreditsError}, as `createArNSAction` maps it.
33
+ * - 409 → {@link ArNSActionExpiredError}: the chain confirmed the signed
34
+ * bytes can never land. The body says whether the credits came back.
35
+ * - 400 `Action <nonce> expired …` → {@link ArNSActionExpiredError}. Matched
36
+ * on THIS nonce, because the same status carries on-chain program
37
+ * rejections ("Lease has expired", "Record is expired") that are not about
38
+ * the action at all.
39
+ * - Anything else — including 503 "Blockhash not found", which the service
40
+ * returns only when it cannot PROVE expiry — is returned unchanged.
41
+ */
42
+ function arNSSignError(nonce, error) {
43
+ if (!(error instanceof FailedRequestError))
44
+ return error;
45
+ const { status, message } = error;
46
+ if (status === 402)
47
+ return new InsufficientCreditsError(message);
48
+ if (status === 409 && /expired/i.test(message)) {
49
+ return new ArNSActionExpiredError(nonce, status, /credits have been returned/i.test(message), message);
50
+ }
51
+ if (status === 400 && message.includes(`Action ${nonce} expired`)) {
52
+ return new ArNSActionExpiredError(nonce, status, /expired and was refunded/i.test(message), message);
53
+ }
54
+ return error;
55
+ }
28
56
  export class TurboUnauthenticatedPaymentService {
29
57
  constructor({ url = defaultPaymentServiceURL, logger = Logger.default, retryConfig = defaultRetryConfig(logger), token = 'arweave', }) {
30
58
  this.logger = logger;
@@ -603,17 +631,34 @@ export class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymen
603
631
  * the signature. Replaying a completed action returns `alreadyCompleted:
604
632
  * true` rather than buying twice, so this is safe to call again if a
605
633
  * response is lost.
634
+ *
635
+ * The route is authorised by the ANT owner's signature INSIDE the
636
+ * transaction, not by the payer: the credits were debited when the action
637
+ * was created. So no payer signature is sent by default — for a browser
638
+ * payer that would be a second wallet prompt inside the ~30 s blockhash
639
+ * window. Pass `headers` only for a service that requires them.
640
+ *
641
+ * Failures: {@link ArNSActionExpiredError} when the action or its
642
+ * transaction has provably expired (create a new action);
643
+ * {@link InsufficientCreditsError} on 402. A 503 "Blockhash not found"
644
+ * means expiry could not be proven — re-posting the SAME signed bytes is
645
+ * idempotent and may still succeed; do not re-create the action.
606
646
  */
607
- async signArNSAction(nonce, signedTransaction) {
608
- return this.httpService.post({
609
- endpoint: `/arns/actions/${nonce}/sign`,
610
- headers: {
611
- ...(await this.signer.generateSignedRequestHeaders(uuidV4())),
612
- 'content-type': 'application/json',
613
- },
614
- data: Buffer.from(JSON.stringify({ transaction: signedTransaction })),
615
- retry: false,
616
- });
647
+ async signArNSAction(nonce, signedTransaction, headers) {
648
+ try {
649
+ return await this.httpService.post({
650
+ endpoint: `/arns/actions/${nonce}/sign`,
651
+ headers: {
652
+ ...(headers ?? {}),
653
+ 'content-type': 'application/json',
654
+ },
655
+ data: Buffer.from(JSON.stringify({ transaction: signedTransaction })),
656
+ retry: false,
657
+ });
658
+ }
659
+ catch (error) {
660
+ throw arNSSignError(nonce, error);
661
+ }
617
662
  }
618
663
  /**
619
664
  * Status of an action by nonce. Open — no signature required — so it works
@@ -640,7 +685,7 @@ export class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymen
640
685
  : undefined);
641
686
  // Fires before any wallet prompt: the action is already debited, so the
642
687
  // caller needs the nonce persisted even if the user walks away here.
643
- await opts.onNonce?.(created.nonce);
688
+ await opts.onNonce?.(created.nonce, created);
644
689
  if (created.status === 'completed')
645
690
  return created;
646
691
  if (owner === undefined) {
@@ -649,6 +694,8 @@ export class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymen
649
694
  `Nonce ${created.nonce} is already debited — poll it rather than re-creating.`);
650
695
  }
651
696
  const signed = await owner.signTransaction(created.transaction);
697
+ // The owner's signature is the only prompt between create and submit:
698
+ // `/sign` needs no payer signature (see `signArNSAction`).
652
699
  return this.signArNSAction(created.nonce, signed);
653
700
  }
654
701
  /**
@@ -17,6 +17,7 @@ import { createAssociatedTokenAccountIdempotentInstruction, createTransferChecke
17
17
  import { Connection, PublicKey, Transaction, TransactionInstruction, } from '@solana/web3.js';
18
18
  import { BigNumber } from 'bignumber.js';
19
19
  import bs58 from 'bs58';
20
+ import { isSolanaWalletAdapter, } from '../../types.js';
20
21
  import { defaultProdGatewayUrls, sleep } from '../../utils/common.js';
21
22
  import { Logger } from '../logger.js';
22
23
  import { memoProgramId } from './solana.js';
@@ -74,11 +75,12 @@ export class SplToken {
74
75
  data: Buffer.from('turboCreditDestinationAddress=' + turboCreditDestinationAddress),
75
76
  }));
76
77
  }
77
- const serializedTx = tx.serializeMessage();
78
- const signature = await signer.signData(Uint8Array.from(serializedTx));
79
- tx.addSignature(ownerPublicKey, Buffer.from(signature));
78
+ const { signedTx, signature } = signer.walletAdapter !== undefined &&
79
+ isSolanaWalletAdapter(signer.walletAdapter)
80
+ ? await this.signWithWallet(tx, signer.walletAdapter)
81
+ : await this.signWithKey(tx, ownerPublicKey, signer);
80
82
  const txId = bs58.encode(signature);
81
- await this.submitTx(tx, txId);
83
+ await this.submitTx(signedTx, txId);
82
84
  this.logger.debug(`Submitted ${this.tokenName} SPL transfer transaction...`, {
83
85
  id: txId,
84
86
  target,
@@ -89,6 +91,32 @@ export class SplToken {
89
91
  });
90
92
  return { id: txId, target, reward: '0' };
91
93
  }
94
+ /** Sign the message bytes directly: a private-key signer holds the key. */
95
+ async signWithKey(tx, ownerPublicKey, signer) {
96
+ const signature = await signer.signData(Uint8Array.from(tx.serializeMessage()));
97
+ tx.addSignature(ownerPublicKey, Buffer.from(signature));
98
+ return { signedTx: tx, signature };
99
+ }
100
+ /**
101
+ * Have a browser wallet sign the transaction itself, as the SOL path does.
102
+ *
103
+ * A wallet signs through `signTransaction`, not `signMessage`: it refuses, or
104
+ * cannot display, a serialized transaction handed to it as a message. It may
105
+ * also add instructions of its own before signing (compute budget, guards),
106
+ * so the transaction submitted is the one it returns, never ours with its
107
+ * signature attached. Deserializing drops `lastValidBlockHeight`, so that is
108
+ * carried over for confirmation.
109
+ */
110
+ async signWithWallet(tx, walletAdapter) {
111
+ const signedTx = await walletAdapter.signTransaction(tx);
112
+ const signature = signedTx.signature;
113
+ if (signature === null) {
114
+ throw new Error(`Wallet returned an unsigned ${this.tokenName} transfer transaction`);
115
+ }
116
+ signedTx.recentBlockhash ??= tx.recentBlockhash;
117
+ signedTx.lastValidBlockHeight ??= tx.lastValidBlockHeight;
118
+ return { signedTx, signature: Uint8Array.from(signature) };
119
+ }
92
120
  async submitTx(tx, id) {
93
121
  this.logger.debug(`Submitting ${this.tokenName} fund transaction...`, {
94
122
  id,
@@ -241,9 +241,13 @@ export class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
241
241
  createArNSAction(action, params, ownerProof) {
242
242
  return this.paymentService.createArNSAction(action, params, ownerProof);
243
243
  }
244
- /** Submit the owner-signed transaction (FULL serialized tx, base64). */
245
- signArNSAction(nonce, signedTransaction) {
246
- return this.paymentService.signArNSAction(nonce, signedTransaction);
244
+ /**
245
+ * Submit the owner-signed transaction (FULL serialized tx, base64).
246
+ * Authorised by the owner's signature inside the transaction; payer
247
+ * `headers` are optional, for services that require them.
248
+ */
249
+ signArNSAction(nonce, signedTransaction, headers) {
250
+ return this.paymentService.signArNSAction(nonce, signedTransaction, headers);
247
251
  }
248
252
  /** Status by nonce — open, needs no signature. Use it to resume. */
249
253
  getArNSActionStatus(nonce) {
package/lib/esm/types.js CHANGED
@@ -28,6 +28,16 @@ export const tokenTypes = [
28
28
  // USDC (SPL) on Solana — signed with the Solana signer, not an EVM one.
29
29
  'solana-usdc',
30
30
  ];
31
+ /**
32
+ * Tokens paid from a Solana wallet: SOL natively, and ARIO and USDC as SPL
33
+ * transfers signed by the same ed25519 key. A private key for any of these
34
+ * builds a Solana signer, so a Solana wallet adapter must as well.
35
+ */
36
+ export const supportedSolanaSignerTokens = new Set([
37
+ 'solana',
38
+ 'ario',
39
+ 'solana-usdc',
40
+ ]);
31
41
  export const supportedEvmSignerTokens = new Set([
32
42
  'ethereum',
33
43
  'base-eth',
@@ -70,6 +70,32 @@ export class FiatPaymentsDisabledError extends BaseError {
70
70
  this.status = 503;
71
71
  }
72
72
  }
73
+ /**
74
+ * Raised when an ArNS action's owner-signed transaction reaches `/sign` too
75
+ * late. Solana accepts the transaction for only about 30 seconds after Turbo
76
+ * builds it; signing again cannot help, because that transaction is dead.
77
+ *
78
+ * `creditsReleased` says whether the credits debited at creation are back:
79
+ * - 409: the chain confirmed the bytes can never land. `true` when the
80
+ * service says "Your credits have been returned"; `false` means its
81
+ * immediate refund did not go through and the reconciler will refund.
82
+ * - 400 `Action <nonce> expired …`: `true` for "expired and was refunded";
83
+ * `false` for the variant refused before submission, refunded
84
+ * automatically once the reservation (`expiresAt`, ~15 min) lapses.
85
+ *
86
+ * Extends {@link FailedRequestError}, so existing `status` checks still work.
87
+ * Recovery: create a NEW action if the change is still wanted.
88
+ */
89
+ export class ArNSActionExpiredError extends FailedRequestError {
90
+ constructor(nonce, status, creditsReleased,
91
+ /** The service's response body, or a `FailedRequestError` message. */
92
+ message) {
93
+ super(message?.replace(/^Failed request \(Status \d+\): /, '') ??
94
+ `ArNS action ${nonce} expired before its signed transaction was submitted.`, status);
95
+ this.nonce = nonce;
96
+ this.creditsReleased = creditsReleased;
97
+ }
98
+ }
73
99
  export class AbortError extends BaseError {
74
100
  constructor(message = 'Request was aborted') {
75
101
  super(message);
@@ -14,4 +14,4 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
17
- export const version = '2.1.0-alpha.1';
17
+ export const version = '2.1.0-alpha.3';
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/common/factory.ts"],"names":[],"mappings":"AAqBA,OAAO,EACL,oBAAoB,EAEpB,+BAA+B,EAC/B,4CAA4C,EAC5C,wCAAwC,EACxC,iCAAiC,EAKlC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,gCAAgC,EAEjC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AAGpB,8BAAsB,gBAAgB;IACpC,SAAS,CAAC,MAAM,CAAC,MAAM,SAAkB;IAGzC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ;IAIlC,MAAM,CAAC,eAAe,CAAC,EACrB,oBAAyB,EACzB,mBAAwB,EACxB,KAAK,GACN,GAAE,iCAAsC;IAqBzC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC3B,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,KAAK,GACN,EAAE,oBAAoB,GAAG,2BAA2B;IAErD,SAAS,CAAC,QAAQ,CAAC,6BAA6B,CAC9C,MAAM,EAAE,4CAA4C,GAAG;QACrD,cAAc,EAAE,gCAAgC,CAAC;KAClD,GACA,wCAAwC;IAE3C,SAAS,CAAC,qBAAqB,CAAC,EAC9B,UAAU,EACV,MAAM,EAAE,cAAc,EACtB,oBAAyB,EACzB,mBAAwB,EACxB,KAAK,EACL,UAAU,EACV,QAAQ,EACR,UAAU,EACV,MAAM,EACN,aAAa,GACd,EAAE,+BAA+B,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE;IA0FvD,OAAO,CAAC,iBAAiB;CAuB1B"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/common/factory.ts"],"names":[],"mappings":"AAqBA,OAAO,EACL,oBAAoB,EAEpB,+BAA+B,EAC/B,4CAA4C,EAC5C,wCAAwC,EACxC,iCAAiC,EAMlC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,gCAAgC,EAEjC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AAGpB,8BAAsB,gBAAgB;IACpC,SAAS,CAAC,MAAM,CAAC,MAAM,SAAkB;IAGzC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ;IAIlC,MAAM,CAAC,eAAe,CAAC,EACrB,oBAAyB,EACzB,mBAAwB,EACxB,KAAK,GACN,GAAE,iCAAsC;IAqBzC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC3B,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,KAAK,GACN,EAAE,oBAAoB,GAAG,2BAA2B;IAErD,SAAS,CAAC,QAAQ,CAAC,6BAA6B,CAC9C,MAAM,EAAE,4CAA4C,GAAG;QACrD,cAAc,EAAE,gCAAgC,CAAC;KAClD,GACA,wCAAwC;IAE3C,SAAS,CAAC,qBAAqB,CAAC,EAC9B,UAAU,EACV,MAAM,EAAE,cAAc,EACtB,oBAAyB,EACzB,mBAAwB,EACxB,KAAK,EACL,UAAU,EACV,QAAQ,EACR,UAAU,EACV,MAAM,EACN,aAAa,GACd,EAAE,+BAA+B,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE;IA0FvD,OAAO,CAAC,iBAAiB;CAuB1B"}