@shunt-network/sdk 1.0.0
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.
- package/dist/clients/typescript/src/__tests__/instruction-parsers.test.d.ts +1 -0
- package/dist/clients/typescript/src/__tests__/voucher-numeric-safety.test.d.ts +1 -0
- package/dist/clients/typescript/src/__tests__/voucher-types.test-d.d.ts +1 -0
- package/dist/clients/typescript/src/generated/accounts/channel.d.ts +59 -0
- package/dist/clients/typescript/src/generated/accounts/index.d.ts +8 -0
- package/dist/clients/typescript/src/generated/errors/index.d.ts +8 -0
- package/dist/clients/typescript/src/generated/errors/paymentChannels.d.ts +84 -0
- package/dist/clients/typescript/src/generated/index.d.ts +13 -0
- package/dist/clients/typescript/src/generated/instructions/distribute.d.ts +90 -0
- package/dist/clients/typescript/src/generated/instructions/emitEvent.d.ts +42 -0
- package/dist/clients/typescript/src/generated/instructions/index.d.ts +17 -0
- package/dist/clients/typescript/src/generated/instructions/open.d.ts +100 -0
- package/dist/clients/typescript/src/generated/instructions/reclaim.d.ts +39 -0
- package/dist/clients/typescript/src/generated/instructions/requestClose.d.ts +39 -0
- package/dist/clients/typescript/src/generated/instructions/seal.d.ts +36 -0
- package/dist/clients/typescript/src/generated/instructions/settle.d.ts +39 -0
- package/dist/clients/typescript/src/generated/instructions/settleAndSeal.d.ts +47 -0
- package/dist/clients/typescript/src/generated/instructions/topUp.d.ts +56 -0
- package/dist/clients/typescript/src/generated/instructions/withdrawPayer.d.ts +51 -0
- package/dist/clients/typescript/src/generated/pdas/eventAuthority.d.ts +11 -0
- package/dist/clients/typescript/src/generated/pdas/index.d.ts +8 -0
- package/dist/clients/typescript/src/generated/programs/index.d.ts +8 -0
- package/dist/clients/typescript/src/generated/programs/paymentChannels.d.ts +80 -0
- package/dist/clients/typescript/src/generated/types/accountDiscriminator.d.ts +16 -0
- package/dist/clients/typescript/src/generated/types/channelStatus.d.ts +18 -0
- package/dist/clients/typescript/src/generated/types/distributeArgs.d.ts +18 -0
- package/dist/clients/typescript/src/generated/types/distributionEntry.d.ts +16 -0
- package/dist/clients/typescript/src/generated/types/index.d.ts +20 -0
- package/dist/clients/typescript/src/generated/types/openArgs.d.ts +26 -0
- package/dist/clients/typescript/src/generated/types/opened.d.ts +19 -0
- package/dist/clients/typescript/src/generated/types/payoutBeneficiary.d.ts +17 -0
- package/dist/clients/typescript/src/generated/types/payoutRedirected.d.ts +26 -0
- package/dist/clients/typescript/src/generated/types/redirectReason.d.ts +18 -0
- package/dist/clients/typescript/src/generated/types/settleAndSealArgs.d.ts +15 -0
- package/dist/clients/typescript/src/generated/types/settlementWatermarks.d.ts +19 -0
- package/dist/clients/typescript/src/generated/types/topUpArgs.d.ts +17 -0
- package/dist/clients/typescript/src/generated/types/voucherArgs.d.ts +23 -0
- package/dist/clients/typescript/src/index.d.ts +1 -0
- package/dist/clients/typescript/src/safe-codecs.d.ts +6 -0
- package/dist/clients/typescript/tsup.config.d.ts +2 -0
- package/dist/index.cjs +1399 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1549 -0
- package/dist/sdk/channel.d.ts +10 -0
- package/dist/sdk/client.d.ts +31 -0
- package/dist/sdk/utils.d.ts +3 -0
- package/dist/sdk/voucher.d.ts +14 -0
- package/package.json +36 -0
- package/src/clients/typescript/package.json +32 -0
- package/src/clients/typescript/src/__tests__/instruction-parsers.test.ts +81 -0
- package/src/clients/typescript/src/__tests__/voucher-numeric-safety.test.ts +176 -0
- package/src/clients/typescript/src/__tests__/voucher-types.test-d.ts +8 -0
- package/src/clients/typescript/src/generated/accounts/channel.ts +192 -0
- package/src/clients/typescript/src/generated/accounts/index.ts +9 -0
- package/src/clients/typescript/src/generated/errors/index.ts +9 -0
- package/src/clients/typescript/src/generated/errors/paymentChannels.ts +251 -0
- package/src/clients/typescript/src/generated/index.ts +14 -0
- package/src/clients/typescript/src/generated/instructions/distribute.ts +517 -0
- package/src/clients/typescript/src/generated/instructions/emitEvent.ts +194 -0
- package/src/clients/typescript/src/generated/instructions/index.ts +18 -0
- package/src/clients/typescript/src/generated/instructions/open.ts +577 -0
- package/src/clients/typescript/src/generated/instructions/reclaim.ts +165 -0
- package/src/clients/typescript/src/generated/instructions/requestClose.ts +173 -0
- package/src/clients/typescript/src/generated/instructions/seal.ts +151 -0
- package/src/clients/typescript/src/generated/instructions/settle.ts +180 -0
- package/src/clients/typescript/src/generated/instructions/settleAndSeal.ts +224 -0
- package/src/clients/typescript/src/generated/instructions/topUp.ts +267 -0
- package/src/clients/typescript/src/generated/instructions/withdrawPayer.ts +246 -0
- package/src/clients/typescript/src/generated/pdas/eventAuthority.ts +26 -0
- package/src/clients/typescript/src/generated/pdas/index.ts +9 -0
- package/src/clients/typescript/src/generated/programs/index.ts +9 -0
- package/src/clients/typescript/src/generated/programs/paymentChannels.ts +366 -0
- package/src/clients/typescript/src/generated/types/accountDiscriminator.ts +41 -0
- package/src/clients/typescript/src/generated/types/channelStatus.ts +40 -0
- package/src/clients/typescript/src/generated/types/distributeArgs.ts +47 -0
- package/src/clients/typescript/src/generated/types/distributionEntry.ts +48 -0
- package/src/clients/typescript/src/generated/types/index.ts +21 -0
- package/src/clients/typescript/src/generated/types/openArgs.ts +70 -0
- package/src/clients/typescript/src/generated/types/opened.ts +42 -0
- package/src/clients/typescript/src/generated/types/payoutBeneficiary.ts +42 -0
- package/src/clients/typescript/src/generated/types/payoutRedirected.ts +76 -0
- package/src/clients/typescript/src/generated/types/redirectReason.ts +40 -0
- package/src/clients/typescript/src/generated/types/settleAndSealArgs.ts +39 -0
- package/src/clients/typescript/src/generated/types/settlementWatermarks.ts +48 -0
- package/src/clients/typescript/src/generated/types/topUpArgs.ts +33 -0
- package/src/clients/typescript/src/generated/types/voucherArgs.ts +68 -0
- package/src/clients/typescript/src/index.ts +1 -0
- package/src/clients/typescript/src/safe-codecs.ts +82 -0
- package/src/clients/typescript/tsconfig.json +11 -0
- package/src/clients/typescript/tsup.config.ts +9 -0
- package/src/index.ts +4 -0
- package/src/sdk/channel.ts +128 -0
- package/src/sdk/client.ts +22 -0
- package/src/sdk/utils.ts +29 -0
- package/src/sdk/voucher.ts +50 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ShuntClient } from "./client.js";
|
|
2
|
+
import { type Address } from "@solana/kit";
|
|
3
|
+
export declare function openPaymentChannel(client: ShuntClient, depositAmount: bigint): Promise<{
|
|
4
|
+
channel: Address;
|
|
5
|
+
signature: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function topUpPaymentChannel(client: ShuntClient, channelAddress: Address, topUpAmount: bigint): Promise<{
|
|
8
|
+
channel: Address;
|
|
9
|
+
signature: string;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const devnet = true;
|
|
2
|
+
export declare function createShuntClient(signerFilePath: string): Promise<import("@solana/plugin-core").Client<{
|
|
3
|
+
planTransaction: (input: import("@solana/instruction-plans").InstructionPlanInput, config?: {
|
|
4
|
+
abortSignal?: AbortSignal;
|
|
5
|
+
}) => Promise<import("@solana/instruction-plans").SingleTransactionPlan['message']>;
|
|
6
|
+
planTransactions: (input: import("@solana/instruction-plans").InstructionPlanInput, config?: {
|
|
7
|
+
abortSignal?: AbortSignal;
|
|
8
|
+
}) => Promise<import("@solana/instruction-plans").TransactionPlan>;
|
|
9
|
+
sendTransaction: (input: import("@solana/instruction-plans").InstructionPlanInput | import("@solana/instruction-plans").SingleTransactionPlan | import("@solana/instruction-plans").SingleTransactionPlan['message'], config?: {
|
|
10
|
+
abortSignal?: AbortSignal;
|
|
11
|
+
}) => Promise<import("@solana/instruction-plans").SuccessfulSingleTransactionPlanResult<import("@solana/kit-plugin-rpc").RpcSendContext>>;
|
|
12
|
+
sendTransactions: (input: import("@solana/instruction-plans").InstructionPlanInput | import("@solana/instruction-plans").TransactionPlanInput, config?: {
|
|
13
|
+
abortSignal?: AbortSignal;
|
|
14
|
+
}) => Promise<import("@solana/instruction-plans").TransactionPlanResult<import("@solana/kit-plugin-rpc").RpcSendContext>>;
|
|
15
|
+
signTransaction: (input: import("@solana/instruction-plans").InstructionPlanInput | import("@solana/instruction-plans").SingleTransactionPlan | import("@solana/instruction-plans").SingleTransactionPlan['message'], config?: {
|
|
16
|
+
abortSignal?: AbortSignal;
|
|
17
|
+
}) => Promise<import("@solana/instruction-plans").SuccessfulSingleTransactionPlanResult<import("@solana/kit-plugin-rpc").RpcSignContext>>;
|
|
18
|
+
signTransactions: (input: import("@solana/instruction-plans").InstructionPlanInput | import("@solana/instruction-plans").TransactionPlanInput, config?: {
|
|
19
|
+
abortSignal?: AbortSignal;
|
|
20
|
+
}) => Promise<import("@solana/instruction-plans").TransactionPlanResult<import("@solana/kit-plugin-rpc").RpcSignContext>>;
|
|
21
|
+
getMinimumBalance: (space: number, config?: import("@solana/plugin-interfaces").GetMinimumBalanceConfig) => Promise<import("@solana/rpc-types").Lamports>;
|
|
22
|
+
payer: import("@solana/signers").TransactionSigner;
|
|
23
|
+
rpc: import("@solana/rpc-spec").Rpc<import("@solana/rpc-api").GetAccountInfoApi & import("@solana/rpc-api").GetBalanceApi & import("@solana/rpc-api").GetBlockApi & import("@solana/rpc-api").GetBlockCommitmentApi & import("@solana/rpc-api").GetBlockHeightApi & import("@solana/rpc-api").GetBlockProductionApi & import("@solana/rpc-api").GetBlocksApi & import("@solana/rpc-api").GetBlocksWithLimitApi & import("@solana/rpc-api").GetBlockTimeApi & import("@solana/rpc-api").GetClusterNodesApi & import("@solana/rpc-api").GetEpochInfoApi & import("@solana/rpc-api").GetEpochScheduleApi & import("@solana/rpc-api").GetFeeForMessageApi & import("@solana/rpc-api").GetFirstAvailableBlockApi & import("@solana/rpc-api").GetGenesisHashApi & import("@solana/rpc-api").GetHealthApi & import("@solana/rpc-api").GetHighestSnapshotSlotApi & import("@solana/rpc-api").GetIdentityApi & import("@solana/rpc-api").GetInflationGovernorApi & import("@solana/rpc-api").GetInflationRateApi & import("@solana/rpc-api").GetInflationRewardApi & import("@solana/rpc-api").GetLargestAccountsApi & import("@solana/rpc-api").GetLatestBlockhashApi & import("@solana/rpc-api").GetLeaderScheduleApi & import("@solana/rpc-api").GetMaxRetransmitSlotApi & import("@solana/rpc-api").GetMaxShredInsertSlotApi & import("@solana/rpc-api").GetMinimumBalanceForRentExemptionApi & import("@solana/rpc-api").GetMultipleAccountsApi & import("@solana/rpc-api").GetProgramAccountsApi & import("@solana/rpc-api").GetRecentPerformanceSamplesApi & import("@solana/rpc-api").GetRecentPrioritizationFeesApi & import("@solana/rpc-api").GetSignaturesForAddressApi & import("@solana/rpc-api").GetSignatureStatusesApi & import("@solana/rpc-api").GetSlotApi & import("@solana/rpc-api").GetSlotLeaderApi & import("@solana/rpc-api").GetSlotLeadersApi & import("@solana/rpc-api").GetStakeMinimumDelegationApi & import("@solana/rpc-api").GetSupplyApi & import("@solana/rpc-api").GetTokenAccountBalanceApi & import("@solana/rpc-api").GetTokenAccountsByDelegateApi & import("@solana/rpc-api").GetTokenAccountsByOwnerApi & import("@solana/rpc-api").GetTokenLargestAccountsApi & import("@solana/rpc-api").GetTokenSupplyApi & import("@solana/rpc-api").GetTransactionApi & import("@solana/rpc-api").GetTransactionCountApi & import("@solana/rpc-api").GetTransactionsForAddressApi & import("@solana/rpc-api").GetVersionApi & import("@solana/rpc-api").GetVoteAccountsApi & import("@solana/rpc-api").IsBlockhashValidApi & import("@solana/rpc-api").MinimumLedgerSlotApi & import("@solana/rpc-api").SendTransactionApi & import("@solana/rpc-api").SimulateTransactionApi>;
|
|
24
|
+
rpcSubscriptions: import("@solana/rpc-subscriptions-spec").RpcSubscriptions<import("@solana/rpc-subscriptions-api").SolanaRpcSubscriptionsApi>;
|
|
25
|
+
transactionPlanner: import("@solana/instruction-plans").TransactionPlanner;
|
|
26
|
+
transactionPlanExecutor: import("@solana/instruction-plans").TransactionPlanExecutor<import("@solana/kit-plugin-rpc").RpcSendContext>;
|
|
27
|
+
system: import("@solana-program/system").SystemPlugin;
|
|
28
|
+
token: import("@solana-program/token").TokenPlugin;
|
|
29
|
+
}>>;
|
|
30
|
+
export type ShuntClient = Awaited<ReturnType<typeof createShuntClient>>;
|
|
31
|
+
export declare const SHUNT_TREASURY = "4gNrRt7FKoetTTGbJuzp6WYtgqqmKLpjXybPqY3zbVCh";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ShuntClient } from "./client.js";
|
|
2
|
+
import { type Address } from "@solana/kit";
|
|
3
|
+
export declare function createSignedVoucher(client: ShuntClient, channelAddress: Address, cumulativeAmount: bigint, expiresAtUnixTimestamp?: bigint): Promise<{
|
|
4
|
+
voucher: {
|
|
5
|
+
voucher: {
|
|
6
|
+
channelId: Address;
|
|
7
|
+
cumulativeAmount: string;
|
|
8
|
+
expiresAt?: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
signer: import("@solana/nominal-types").NominalType<"brand", "Address"> & import("@solana/nominal-types").NominalType<"stringEncoding", "base58"> & string;
|
|
11
|
+
signature: string;
|
|
12
|
+
signatureType: string;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shunt-network/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "Aditya Patwa",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format cjs,esm --clean && tsc --emitDeclarationOnly --declaration --outDir dist --skipLibCheck --module esnext --moduleResolution bundler",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^26.5.0",
|
|
22
|
+
"tsup": "^8.5.1",
|
|
23
|
+
"typescript": "^7.0.2",
|
|
24
|
+
"vitest": "^5.0.1"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@solana-program/system": "^0.14.1",
|
|
28
|
+
"@solana-program/token": "^0.16.1",
|
|
29
|
+
"@solana/kit": "^8.2.0",
|
|
30
|
+
"@solana/kit-plugin-rpc": "^0.19.0",
|
|
31
|
+
"@solana/kit-plugin-signer": "^0.19.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@payment-channels/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"format": "biome format --write .",
|
|
19
|
+
"format:check": "biome format .",
|
|
20
|
+
"clean": "rm -rf dist src/generated"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@solana/kit": "^6.1.0",
|
|
24
|
+
"@solana/program-client-core": "^6.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsup": "^8.3.0",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"vitest": "^2.1.8",
|
|
30
|
+
"@types/node": "^25.0.10"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccountRole,
|
|
3
|
+
createNoopSigner,
|
|
4
|
+
getAddressDecoder,
|
|
5
|
+
isSolanaError,
|
|
6
|
+
SOLANA_ERROR__PROGRAM_CLIENTS__INSUFFICIENT_ACCOUNT_METAS,
|
|
7
|
+
type AccountMeta,
|
|
8
|
+
type Address,
|
|
9
|
+
} from '@solana/kit';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
getDistributeInstructionDataEncoder,
|
|
13
|
+
getRequestCloseInstruction,
|
|
14
|
+
PAYMENT_CHANNELS_PROGRAM_ADDRESS,
|
|
15
|
+
parseDistributeInstruction,
|
|
16
|
+
parseRequestCloseInstruction,
|
|
17
|
+
} from '../generated/index.js';
|
|
18
|
+
|
|
19
|
+
// Any valid base58 address works; system program is the easiest to grab.
|
|
20
|
+
const SYSTEM_ADDR = '11111111111111111111111111111111' as Address;
|
|
21
|
+
const EXTRA_META: AccountMeta = { address: SYSTEM_ADDR, role: AccountRole.READONLY };
|
|
22
|
+
|
|
23
|
+
describe('fixed-shape parsers mirror the on-chain exact account lists', () => {
|
|
24
|
+
const exact = getRequestCloseInstruction({
|
|
25
|
+
payer: createNoopSigner(SYSTEM_ADDR),
|
|
26
|
+
channel: SYSTEM_ADDR,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('parses a builder-produced requestClose instruction (exactly 2 accounts)', () => {
|
|
30
|
+
const parsed = parseRequestCloseInstruction(exact);
|
|
31
|
+
expect(parsed.accounts.payer.address).toBe(SYSTEM_ADDR);
|
|
32
|
+
expect(parsed.accounts.channel.address).toBe(SYSTEM_ADDR);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rejects a requestClose instruction with one extra account', () => {
|
|
36
|
+
const oversupplied = { ...exact, accounts: [...exact.accounts, EXTRA_META] };
|
|
37
|
+
let thrown: unknown;
|
|
38
|
+
try {
|
|
39
|
+
parseRequestCloseInstruction(oversupplied);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
thrown = error;
|
|
42
|
+
}
|
|
43
|
+
// The kit has no wrong-account-count error, so the parser reuses the
|
|
44
|
+
// insufficient-metas one for the too-many direction as well.
|
|
45
|
+
expect(isSolanaError(thrown, SOLANA_ERROR__PROGRAM_CLIENTS__INSUFFICIENT_ACCOUNT_METAS)).toBe(
|
|
46
|
+
true,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('still parses distribute with a remaining-accounts tail (11 fixed + 2 extra)', () => {
|
|
51
|
+
// Distinct address per slot so the assertion pins positional mapping.
|
|
52
|
+
const metas: AccountMeta[] = Array.from({ length: 13 }, (_, i) => ({
|
|
53
|
+
address: getAddressDecoder().decode(new Uint8Array(32).fill(i + 1)),
|
|
54
|
+
role: AccountRole.READONLY,
|
|
55
|
+
}));
|
|
56
|
+
const distribute = {
|
|
57
|
+
programAddress: PAYMENT_CHANNELS_PROGRAM_ADDRESS,
|
|
58
|
+
accounts: metas,
|
|
59
|
+
data: getDistributeInstructionDataEncoder().encode({
|
|
60
|
+
distributeArgs: { recipients: [] },
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
const parsed = parseDistributeInstruction(distribute);
|
|
64
|
+
const named = [
|
|
65
|
+
parsed.accounts.channel,
|
|
66
|
+
parsed.accounts.payer,
|
|
67
|
+
parsed.accounts.rentPayer,
|
|
68
|
+
parsed.accounts.channelTokenAccount,
|
|
69
|
+
parsed.accounts.payerTokenAccount,
|
|
70
|
+
parsed.accounts.payeeTokenAccount,
|
|
71
|
+
parsed.accounts.treasuryTokenAccount,
|
|
72
|
+
parsed.accounts.mint,
|
|
73
|
+
parsed.accounts.tokenProgram,
|
|
74
|
+
parsed.accounts.eventAuthority,
|
|
75
|
+
parsed.accounts.selfProgram,
|
|
76
|
+
];
|
|
77
|
+
expect(named.map((meta) => meta.address)).toEqual(
|
|
78
|
+
metas.slice(0, 11).map((meta) => meta.address),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Address } from '@solana/kit';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
getOpenArgsEncoder,
|
|
5
|
+
getTopUpArgsEncoder,
|
|
6
|
+
getVoucherArgsDecoder,
|
|
7
|
+
getVoucherArgsEncoder,
|
|
8
|
+
} from '../generated/index.js';
|
|
9
|
+
import { getI64Encoder, getU64Encoder } from '../safe-codecs.js';
|
|
10
|
+
|
|
11
|
+
// Any valid base58 address works; system program is the easiest to grab.
|
|
12
|
+
const SYSTEM_ADDR = '11111111111111111111111111111111' as Address;
|
|
13
|
+
|
|
14
|
+
// Domain-separation prefix of the voucher payload: tag 'V' + version 0x01.
|
|
15
|
+
const MAGIC = [0x56, 0x01];
|
|
16
|
+
|
|
17
|
+
describe('safe u64/i64 encoders runtime-reject lossy JS numbers', () => {
|
|
18
|
+
it('throws TypeError on cumulativeAmount above 2^53-1 passed as number', () => {
|
|
19
|
+
const encoder = getVoucherArgsEncoder();
|
|
20
|
+
// TS blocks this, but JS callers can still pass a raw number.
|
|
21
|
+
// The error message should mention bigint so the caller knows what to do.
|
|
22
|
+
expect(() =>
|
|
23
|
+
encoder.encode({
|
|
24
|
+
magic: MAGIC,
|
|
25
|
+
channelId: SYSTEM_ADDR,
|
|
26
|
+
cumulativeAmount: 9007199254740993 as unknown as bigint,
|
|
27
|
+
expiresAt: 0n,
|
|
28
|
+
}),
|
|
29
|
+
).toThrow(
|
|
30
|
+
expect.objectContaining({
|
|
31
|
+
constructor: TypeError,
|
|
32
|
+
message: expect.stringMatching(/bigint/i),
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('produces distinct images for 2^53 and 2^53 + 1 (regression for the SOLA4-2 root cause)', () => {
|
|
38
|
+
const encoder = getVoucherArgsEncoder();
|
|
39
|
+
const a = encoder.encode({
|
|
40
|
+
magic: MAGIC,
|
|
41
|
+
channelId: SYSTEM_ADDR,
|
|
42
|
+
cumulativeAmount: 2n ** 53n,
|
|
43
|
+
expiresAt: 0n,
|
|
44
|
+
});
|
|
45
|
+
const b = encoder.encode({
|
|
46
|
+
magic: MAGIC,
|
|
47
|
+
channelId: SYSTEM_ADDR,
|
|
48
|
+
cumulativeAmount: 2n ** 53n + 1n,
|
|
49
|
+
expiresAt: 0n,
|
|
50
|
+
});
|
|
51
|
+
expect(a.length).toBe(50);
|
|
52
|
+
expect(b.length).toBe(50);
|
|
53
|
+
expect(Buffer.from(a).equals(Buffer.from(b))).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('round-trips bigint cumulativeAmount through encoder/decoder as bigint', () => {
|
|
57
|
+
const bytes = getVoucherArgsEncoder().encode({
|
|
58
|
+
magic: MAGIC,
|
|
59
|
+
channelId: SYSTEM_ADDR,
|
|
60
|
+
cumulativeAmount: 100n,
|
|
61
|
+
expiresAt: 0n,
|
|
62
|
+
});
|
|
63
|
+
const decoded = getVoucherArgsDecoder().decode(bytes);
|
|
64
|
+
expect(decoded.cumulativeAmount).toBe(100n);
|
|
65
|
+
// Should come back as bigint, not number.
|
|
66
|
+
expect(typeof decoded.cumulativeAmount).toBe('bigint');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('accepts safe-integer JS number at runtime and matches bigint encoding', () => {
|
|
70
|
+
const encoder = getVoucherArgsEncoder();
|
|
71
|
+
const fromNumber = encoder.encode({
|
|
72
|
+
magic: MAGIC,
|
|
73
|
+
channelId: SYSTEM_ADDR,
|
|
74
|
+
cumulativeAmount: 100 as unknown as bigint,
|
|
75
|
+
expiresAt: 0n,
|
|
76
|
+
});
|
|
77
|
+
const fromBigint = encoder.encode({
|
|
78
|
+
magic: MAGIC,
|
|
79
|
+
channelId: SYSTEM_ADDR,
|
|
80
|
+
cumulativeAmount: 100n,
|
|
81
|
+
expiresAt: 0n,
|
|
82
|
+
});
|
|
83
|
+
expect(Buffer.from(fromNumber).equals(Buffer.from(fromBigint))).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('accepts negative i64 expiresAt and rejects overflow', () => {
|
|
87
|
+
const encoder = getVoucherArgsEncoder();
|
|
88
|
+
// -1n is valid i64
|
|
89
|
+
expect(() =>
|
|
90
|
+
encoder.encode({
|
|
91
|
+
magic: MAGIC,
|
|
92
|
+
channelId: SYSTEM_ADDR,
|
|
93
|
+
cumulativeAmount: 0n,
|
|
94
|
+
expiresAt: -1n,
|
|
95
|
+
}),
|
|
96
|
+
).not.toThrow();
|
|
97
|
+
// 2^63 overflows signed i64
|
|
98
|
+
expect(() =>
|
|
99
|
+
encoder.encode({
|
|
100
|
+
magic: MAGIC,
|
|
101
|
+
channelId: SYSTEM_ADDR,
|
|
102
|
+
cumulativeAmount: 0n,
|
|
103
|
+
expiresAt: 2n ** 63n,
|
|
104
|
+
}),
|
|
105
|
+
).toThrow(RangeError);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('rejects negative u64 and u64 overflow at the raw encoder', () => {
|
|
109
|
+
const u64 = getU64Encoder();
|
|
110
|
+
expect(() => u64.encode(-1n)).toThrow(RangeError);
|
|
111
|
+
expect(() => u64.encode(2n ** 64n)).toThrow(RangeError);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('accepts the inclusive u64 boundaries (0n and 2^64 - 1)', () => {
|
|
115
|
+
// Both endpoints inclusive; catches off-by-one in the range check.
|
|
116
|
+
const u64 = getU64Encoder();
|
|
117
|
+
expect(() => u64.encode(0n)).not.toThrow();
|
|
118
|
+
expect(() => u64.encode(2n ** 64n - 1n)).not.toThrow();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('also reaches the i64 raw encoder for direct callers', () => {
|
|
122
|
+
const i64 = getI64Encoder();
|
|
123
|
+
// sanity check
|
|
124
|
+
expect(() => i64.encode(0n)).not.toThrow();
|
|
125
|
+
// unsafe number
|
|
126
|
+
expect(() => i64.encode(9007199254740993 as unknown as bigint)).toThrow(TypeError);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('accepts the inclusive i64 boundaries and rejects one past either side', () => {
|
|
130
|
+
const i64 = getI64Encoder();
|
|
131
|
+
// Both i64 endpoints valid; one past either side overflows.
|
|
132
|
+
expect(() => i64.encode(-(2n ** 63n))).not.toThrow();
|
|
133
|
+
expect(() => i64.encode(2n ** 63n - 1n)).not.toThrow();
|
|
134
|
+
expect(() => i64.encode(-(2n ** 63n) - 1n)).toThrow(RangeError);
|
|
135
|
+
expect(() => i64.encode(2n ** 63n)).toThrow(RangeError);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('rejects unsafe-integer numbers in OpenArgs.deposit (u64-rich coverage)', () => {
|
|
139
|
+
const encoder = getOpenArgsEncoder();
|
|
140
|
+
expect(() =>
|
|
141
|
+
encoder.encode({
|
|
142
|
+
salt: 0n,
|
|
143
|
+
deposit: 9007199254740993 as unknown as bigint,
|
|
144
|
+
gracePeriod: 0,
|
|
145
|
+
openSlot: 0n,
|
|
146
|
+
recipients: [],
|
|
147
|
+
}),
|
|
148
|
+
).toThrow(TypeError);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('rejects unsafe-integer salt in OpenArgs', () => {
|
|
152
|
+
const encoder = getOpenArgsEncoder();
|
|
153
|
+
expect(() =>
|
|
154
|
+
encoder.encode({
|
|
155
|
+
salt: 9007199254740993 as unknown as bigint,
|
|
156
|
+
deposit: 0n,
|
|
157
|
+
gracePeriod: 0,
|
|
158
|
+
openSlot: 0n,
|
|
159
|
+
recipients: [],
|
|
160
|
+
}),
|
|
161
|
+
).toThrow(TypeError);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('rejects unsafe-integer TopUpArgs.amount', () => {
|
|
165
|
+
const encoder = getTopUpArgsEncoder();
|
|
166
|
+
expect(() => encoder.encode({ amount: 9007199254740993 as unknown as bigint })).toThrow(
|
|
167
|
+
TypeError,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// (The former SettleAndSealArgs voucher-embedding tests were removed:
|
|
172
|
+
// `settleAndSeal` no longer carries a voucher in its instruction data —
|
|
173
|
+
// the voucher rides in the bundled Ed25519 precompile ix. Voucher numeric
|
|
174
|
+
// safety is covered directly via getVoucherArgsEncoder above, which is the
|
|
175
|
+
// encoder the off-chain signer uses to build that precompile payload.)
|
|
176
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SOLA4-2 type regression: 64-bit VoucherArgs fields must be `bigint`,
|
|
2
|
+
// not `number | bigint`. Checked by `tsc --noEmit` (vitest skips .test-d.ts).
|
|
3
|
+
import { expectTypeOf } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import type { VoucherArgsArgs } from '../generated/types/voucherArgs.js';
|
|
6
|
+
|
|
7
|
+
expectTypeOf<VoucherArgsArgs['cumulativeAmount']>().toEqualTypeOf<bigint>();
|
|
8
|
+
expectTypeOf<VoucherArgsArgs['expiresAt']>().toEqualTypeOf<bigint>();
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was AUTOGENERATED using the Codama library.
|
|
3
|
+
* Please DO NOT EDIT THIS FILE, instead use visitors
|
|
4
|
+
* to add features, then rerun Codama to update it.
|
|
5
|
+
*
|
|
6
|
+
* @see https://github.com/codama-idl/codama
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
assertAccountExists,
|
|
11
|
+
assertAccountsExist,
|
|
12
|
+
combineCodec,
|
|
13
|
+
decodeAccount,
|
|
14
|
+
fetchEncodedAccount,
|
|
15
|
+
fetchEncodedAccounts,
|
|
16
|
+
getAddressDecoder,
|
|
17
|
+
getAddressEncoder,
|
|
18
|
+
getArrayDecoder,
|
|
19
|
+
getArrayEncoder,
|
|
20
|
+
getStructDecoder,
|
|
21
|
+
getStructEncoder,
|
|
22
|
+
type Account,
|
|
23
|
+
type Address,
|
|
24
|
+
type EncodedAccount,
|
|
25
|
+
type FetchAccountConfig,
|
|
26
|
+
type FetchAccountsConfig,
|
|
27
|
+
type FixedSizeCodec,
|
|
28
|
+
type FixedSizeDecoder,
|
|
29
|
+
type FixedSizeEncoder,
|
|
30
|
+
type MaybeAccount,
|
|
31
|
+
type MaybeEncodedAccount,
|
|
32
|
+
} from "@solana/kit";
|
|
33
|
+
import {
|
|
34
|
+
getI64Decoder,
|
|
35
|
+
getI64Encoder,
|
|
36
|
+
getU32Decoder,
|
|
37
|
+
getU32Encoder,
|
|
38
|
+
getU64Decoder,
|
|
39
|
+
getU64Encoder,
|
|
40
|
+
getU8Decoder,
|
|
41
|
+
getU8Encoder,
|
|
42
|
+
} from "../../safe-codecs.js";
|
|
43
|
+
import {
|
|
44
|
+
getSettlementWatermarksDecoder,
|
|
45
|
+
getSettlementWatermarksEncoder,
|
|
46
|
+
type SettlementWatermarks,
|
|
47
|
+
type SettlementWatermarksArgs,
|
|
48
|
+
} from "../types";
|
|
49
|
+
|
|
50
|
+
export type Channel = {
|
|
51
|
+
discriminator: number;
|
|
52
|
+
version: number;
|
|
53
|
+
bump: number;
|
|
54
|
+
status: number;
|
|
55
|
+
salt: bigint;
|
|
56
|
+
deposit: bigint;
|
|
57
|
+
settlement: SettlementWatermarks;
|
|
58
|
+
closureStartedAt: bigint;
|
|
59
|
+
payerWithdrawnAt: bigint;
|
|
60
|
+
gracePeriod: number;
|
|
61
|
+
distributionHash: Array<number>;
|
|
62
|
+
payer: Address;
|
|
63
|
+
payee: Address;
|
|
64
|
+
authorizedSigner: Address;
|
|
65
|
+
mint: Address;
|
|
66
|
+
rentPayer: Address;
|
|
67
|
+
openSlot: bigint;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type ChannelArgs = {
|
|
71
|
+
discriminator: number;
|
|
72
|
+
version: number;
|
|
73
|
+
bump: number;
|
|
74
|
+
status: number;
|
|
75
|
+
salt: bigint;
|
|
76
|
+
deposit: bigint;
|
|
77
|
+
settlement: SettlementWatermarksArgs;
|
|
78
|
+
closureStartedAt: bigint;
|
|
79
|
+
payerWithdrawnAt: bigint;
|
|
80
|
+
gracePeriod: number;
|
|
81
|
+
distributionHash: Array<number>;
|
|
82
|
+
payer: Address;
|
|
83
|
+
payee: Address;
|
|
84
|
+
authorizedSigner: Address;
|
|
85
|
+
mint: Address;
|
|
86
|
+
rentPayer: Address;
|
|
87
|
+
openSlot: bigint;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Gets the encoder for {@link ChannelArgs} account data. */
|
|
91
|
+
export function getChannelEncoder(): FixedSizeEncoder<ChannelArgs> {
|
|
92
|
+
return getStructEncoder([
|
|
93
|
+
["discriminator", getU8Encoder()],
|
|
94
|
+
["version", getU8Encoder()],
|
|
95
|
+
["bump", getU8Encoder()],
|
|
96
|
+
["status", getU8Encoder()],
|
|
97
|
+
["salt", getU64Encoder()],
|
|
98
|
+
["deposit", getU64Encoder()],
|
|
99
|
+
["settlement", getSettlementWatermarksEncoder()],
|
|
100
|
+
["closureStartedAt", getI64Encoder()],
|
|
101
|
+
["payerWithdrawnAt", getI64Encoder()],
|
|
102
|
+
["gracePeriod", getU32Encoder()],
|
|
103
|
+
["distributionHash", getArrayEncoder(getU8Encoder(), { size: 32 })],
|
|
104
|
+
["payer", getAddressEncoder()],
|
|
105
|
+
["payee", getAddressEncoder()],
|
|
106
|
+
["authorizedSigner", getAddressEncoder()],
|
|
107
|
+
["mint", getAddressEncoder()],
|
|
108
|
+
["rentPayer", getAddressEncoder()],
|
|
109
|
+
["openSlot", getU64Encoder()],
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Gets the decoder for {@link Channel} account data. */
|
|
114
|
+
export function getChannelDecoder(): FixedSizeDecoder<Channel> {
|
|
115
|
+
return getStructDecoder([
|
|
116
|
+
["discriminator", getU8Decoder()],
|
|
117
|
+
["version", getU8Decoder()],
|
|
118
|
+
["bump", getU8Decoder()],
|
|
119
|
+
["status", getU8Decoder()],
|
|
120
|
+
["salt", getU64Decoder()],
|
|
121
|
+
["deposit", getU64Decoder()],
|
|
122
|
+
["settlement", getSettlementWatermarksDecoder()],
|
|
123
|
+
["closureStartedAt", getI64Decoder()],
|
|
124
|
+
["payerWithdrawnAt", getI64Decoder()],
|
|
125
|
+
["gracePeriod", getU32Decoder()],
|
|
126
|
+
["distributionHash", getArrayDecoder(getU8Decoder(), { size: 32 })],
|
|
127
|
+
["payer", getAddressDecoder()],
|
|
128
|
+
["payee", getAddressDecoder()],
|
|
129
|
+
["authorizedSigner", getAddressDecoder()],
|
|
130
|
+
["mint", getAddressDecoder()],
|
|
131
|
+
["rentPayer", getAddressDecoder()],
|
|
132
|
+
["openSlot", getU64Decoder()],
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Gets the codec for {@link Channel} account data. */
|
|
137
|
+
export function getChannelCodec(): FixedSizeCodec<ChannelArgs, Channel> {
|
|
138
|
+
return combineCodec(getChannelEncoder(), getChannelDecoder());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function decodeChannel<TAddress extends string = string>(
|
|
142
|
+
encodedAccount: EncodedAccount<TAddress>,
|
|
143
|
+
): Account<Channel, TAddress>;
|
|
144
|
+
export function decodeChannel<TAddress extends string = string>(
|
|
145
|
+
encodedAccount: MaybeEncodedAccount<TAddress>,
|
|
146
|
+
): MaybeAccount<Channel, TAddress>;
|
|
147
|
+
export function decodeChannel<TAddress extends string = string>(
|
|
148
|
+
encodedAccount: EncodedAccount<TAddress> | MaybeEncodedAccount<TAddress>,
|
|
149
|
+
): Account<Channel, TAddress> | MaybeAccount<Channel, TAddress> {
|
|
150
|
+
return decodeAccount(
|
|
151
|
+
encodedAccount as MaybeEncodedAccount<TAddress>,
|
|
152
|
+
getChannelDecoder(),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function fetchChannel<TAddress extends string = string>(
|
|
157
|
+
rpc: Parameters<typeof fetchEncodedAccount>[0],
|
|
158
|
+
address: Address<TAddress>,
|
|
159
|
+
config?: FetchAccountConfig,
|
|
160
|
+
): Promise<Account<Channel, TAddress>> {
|
|
161
|
+
const maybeAccount = await fetchMaybeChannel(rpc, address, config);
|
|
162
|
+
assertAccountExists(maybeAccount);
|
|
163
|
+
return maybeAccount;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function fetchMaybeChannel<TAddress extends string = string>(
|
|
167
|
+
rpc: Parameters<typeof fetchEncodedAccount>[0],
|
|
168
|
+
address: Address<TAddress>,
|
|
169
|
+
config?: FetchAccountConfig,
|
|
170
|
+
): Promise<MaybeAccount<Channel, TAddress>> {
|
|
171
|
+
const maybeAccount = await fetchEncodedAccount(rpc, address, config);
|
|
172
|
+
return decodeChannel(maybeAccount);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function fetchAllChannel(
|
|
176
|
+
rpc: Parameters<typeof fetchEncodedAccounts>[0],
|
|
177
|
+
addresses: Array<Address>,
|
|
178
|
+
config?: FetchAccountsConfig,
|
|
179
|
+
): Promise<Account<Channel>[]> {
|
|
180
|
+
const maybeAccounts = await fetchAllMaybeChannel(rpc, addresses, config);
|
|
181
|
+
assertAccountsExist(maybeAccounts);
|
|
182
|
+
return maybeAccounts;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function fetchAllMaybeChannel(
|
|
186
|
+
rpc: Parameters<typeof fetchEncodedAccounts>[0],
|
|
187
|
+
addresses: Array<Address>,
|
|
188
|
+
config?: FetchAccountsConfig,
|
|
189
|
+
): Promise<MaybeAccount<Channel>[]> {
|
|
190
|
+
const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config);
|
|
191
|
+
return maybeAccounts.map((maybeAccount) => decodeChannel(maybeAccount));
|
|
192
|
+
}
|