Skip to content

Commit 021afc7

Browse files
committed
refactor(escrow): replace borsh helper with kit struct codecs
Both escrow examples wrapped a local borshSerialize(schema, data) helper to build instruction data and to decode the Offer account. Replaces it with getStructEncoder and getStructDecoder, matching the shape already used in basics/close-account/native, and drops the borsh dependency. Field widths and order were checked against each program's Rust source. The decoder's fixed size of 113 matches Offer::LEN, and decoding the pubkey fields with getAddressDecoder yields Address values directly, so the tests no longer round-trip them through a raw byte array.
1 parent d4aff7f commit 021afc7

10 files changed

Lines changed: 78 additions & 149 deletions

File tree

tokens/escrow/native/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"@types/chai": "^5.2.3",
1515
"@types/mocha": "^10.0.10",
1616
"@types/node": "^26.1.0",
17-
"borsh": "^2.0.0",
1817
"chai": "^6.2.2",
1918
"mocha": "^11.7.5",
2019
"litesvm": "^1.3.0",

tokens/escrow/native/pnpm-lock.yaml

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
import * as borsh from 'borsh';
1+
import { getAddressDecoder, getStructDecoder, getU8Decoder, getU64Decoder } from '@solana/kit';
22

3-
export const OfferSchema = {
4-
struct: {
5-
id: 'u64',
6-
maker: { array: { type: 'u8', len: 32 } },
7-
token_mint_a: { array: { type: 'u8', len: 32 } },
8-
token_mint_b: { array: { type: 'u8', len: 32 } },
9-
token_b_wanted_amount: 'u64',
10-
bump: 'u8',
11-
},
12-
};
13-
14-
export type OfferRaw = {
15-
id: bigint;
16-
maker: Uint8Array;
17-
token_mint_a: Uint8Array;
18-
token_mint_b: Uint8Array;
19-
token_b_wanted_amount: bigint;
20-
bump: number;
21-
};
22-
23-
export function borshSerialize(schema: borsh.Schema, data: object): Buffer {
24-
return Buffer.from(borsh.serialize(schema, data));
25-
}
3+
// Account data layout, matching the program's `Offer` struct.
4+
export const offerDecoder = getStructDecoder([
5+
['id', getU64Decoder()],
6+
['maker', getAddressDecoder()],
7+
['token_mint_a', getAddressDecoder()],
8+
['token_mint_b', getAddressDecoder()],
9+
['token_b_wanted_amount', getU64Decoder()],
10+
['bump', getU8Decoder()],
11+
]);

tokens/escrow/native/tests/instruction.ts

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,33 @@
1-
import { AccountRole, type Address, type TransactionSigner } from '@solana/kit';
1+
import {
2+
AccountRole,
3+
type Address,
4+
getStructEncoder,
5+
getU8Encoder,
6+
getU64Encoder,
7+
type TransactionSigner,
8+
} from '@solana/kit';
29
import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
310
import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
4-
import * as borsh from 'borsh';
511

612
enum EscrowInstruction {
713
MakeOffer = 0,
814
TakeOffer = 1,
915
RefundOffer = 2,
1016
}
1117

12-
const MakeOfferSchema = {
13-
struct: {
14-
instruction: 'u8',
15-
id: 'u64',
16-
token_a_offered_amount: 'u64',
17-
token_b_wanted_amount: 'u64',
18-
},
19-
};
18+
// Instruction data layout, matching the program's `EscrowInstruction::MakeOffer(MakeOffer)` variant.
19+
const makeOfferEncoder = getStructEncoder([
20+
['instruction', getU8Encoder()],
21+
['id', getU64Encoder()],
22+
['token_a_offered_amount', getU64Encoder()],
23+
['token_b_wanted_amount', getU64Encoder()],
24+
]);
2025

21-
const TakeOfferSchema = {
22-
struct: {
23-
instruction: 'u8',
24-
},
25-
};
26+
// Instruction data layout, matching the program's `EscrowInstruction::TakeOffer` variant.
27+
const takeOfferEncoder = getStructEncoder([['instruction', getU8Encoder()]]);
2628

27-
const RefundOfferSchema = {
28-
struct: {
29-
instruction: 'u8',
30-
},
31-
};
32-
33-
function borshSerialize(schema: borsh.Schema, data: object): Uint8Array {
34-
return borsh.serialize(schema, data);
35-
}
29+
// Instruction data layout, matching the program's `EscrowInstruction::RefundOffer` variant.
30+
const refundOfferEncoder = getStructEncoder([['instruction', getU8Encoder()]]);
3631

3732
export function buildMakeOffer(props: {
3833
id: bigint;
@@ -47,7 +42,7 @@ export function buildMakeOffer(props: {
4742
payer: TransactionSigner;
4843
programId: Address;
4944
}) {
50-
const data = borshSerialize(MakeOfferSchema, {
45+
const data = makeOfferEncoder.encode({
5146
instruction: EscrowInstruction.MakeOffer,
5247
id: props.id,
5348
token_a_offered_amount: props.token_a_offered_amount,
@@ -85,7 +80,7 @@ export function buildTakeOffer(props: {
8580
payer: TransactionSigner;
8681
programId: Address;
8782
}) {
88-
const data = borshSerialize(TakeOfferSchema, {
83+
const data = takeOfferEncoder.encode({
8984
instruction: EscrowInstruction.TakeOffer,
9085
});
9186

@@ -118,7 +113,7 @@ export function buildRefundOffer(props: {
118113
maker: TransactionSigner;
119114
programId: Address;
120115
}) {
121-
const data = borshSerialize(RefundOfferSchema, {
116+
const data = refundOfferEncoder.encode({
122117
instruction: EscrowInstruction.RefundOffer,
123118
});
124119

tokens/escrow/native/tests/test.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Buffer } from 'node:buffer';
21
import {
32
appendTransactionMessageInstruction,
43
createTransactionMessage,
54
generateKeyPairSigner,
6-
getAddressDecoder,
75
type Instruction,
86
type KeyPairSigner,
97
lamports,
@@ -20,17 +18,14 @@ import {
2018
TOKEN_PROGRAM_ADDRESS,
2119
} from '@solana-program/token';
2220
import { getCreateAccountInstruction } from '@solana-program/system';
23-
import * as borsh from 'borsh';
2421
import { assert } from 'chai';
2522
import { FailedTransactionMetadata, LiteSVM } from 'litesvm';
26-
import { type OfferRaw, OfferSchema } from './account';
23+
import { offerDecoder } from './account';
2724
import { buildMakeOffer, buildRefundOffer, buildTakeOffer } from './instruction';
2825
import { createValues, type TestValues, mintingTokens } from './utils';
2926

3027
const LAMPORTS_PER_SOL = 1_000_000_000n;
3128

32-
const addressDecoder = getAddressDecoder();
33-
3429
describe('Escrow!', () => {
3530
const svm = new LiteSVM();
3631
let values: TestValues;
@@ -97,17 +92,16 @@ describe('Escrow!', () => {
9792

9893
const offerInfo = svm.getAccount(values.offer);
9994
assert(offerInfo.exists, 'offer account not created');
100-
const offer = borsh.deserialize(OfferSchema, Buffer.from(offerInfo.data)) as OfferRaw;
95+
const offer = offerDecoder.decode(offerInfo.data);
10196

10297
const vaultInfo = svm.getAccount(values.vault);
10398
assert(vaultInfo.exists, 'vault account not created');
10499
const vaultTokenAccount = getTokenDecoder().decode(vaultInfo.data);
105100

106101
assert(offer.id.toString() === values.id.toString(), 'wrong id');
107-
// borsh deserializes pubkeys as raw byte arrays, decode them into addresses for comparison
108-
assert(addressDecoder.decode(offer.maker) === values.maker.address, 'maker key does not match');
109-
assert(addressDecoder.decode(offer.token_mint_a) === values.mintAKeypair.address, 'wrong mint A');
110-
assert(addressDecoder.decode(offer.token_mint_b) === values.mintBKeypair.address, 'wrong mint B');
102+
assert(offer.maker === values.maker.address, 'maker key does not match');
103+
assert(offer.token_mint_a === values.mintAKeypair.address, 'wrong mint A');
104+
assert(offer.token_mint_b === values.mintBKeypair.address, 'wrong mint B');
111105
assert(offer.token_b_wanted_amount.toString() === values.amountB.toString(), 'unexpected amount B');
112106
assert(vaultTokenAccount.amount.toString() === values.amountA.toString(), 'unexpected amount A');
113107
});

tokens/escrow/pinocchio/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"@types/chai": "^5.2.3",
1515
"@types/mocha": "^10.0.10",
1616
"@types/node": "^26.1.0",
17-
"borsh": "^2.0.0",
1817
"chai": "^6.2.2",
1918
"mocha": "^11.7.5",
2019
"litesvm": "^1.3.0",

tokens/escrow/pinocchio/pnpm-lock.yaml

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
import * as borsh from 'borsh';
1+
import { getAddressDecoder, getStructDecoder, getU8Decoder, getU64Decoder } from '@solana/kit';
22

3-
export const OfferSchema = {
4-
struct: {
5-
id: 'u64',
6-
maker: { array: { type: 'u8', len: 32 } },
7-
token_mint_a: { array: { type: 'u8', len: 32 } },
8-
token_mint_b: { array: { type: 'u8', len: 32 } },
9-
token_b_wanted_amount: 'u64',
10-
bump: 'u8',
11-
},
12-
};
13-
14-
export type OfferRaw = {
15-
id: bigint;
16-
maker: Uint8Array;
17-
token_mint_a: Uint8Array;
18-
token_mint_b: Uint8Array;
19-
token_b_wanted_amount: bigint;
20-
bump: number;
21-
};
22-
23-
export function borshSerialize(schema: borsh.Schema, data: object): Buffer {
24-
return Buffer.from(borsh.serialize(schema, data));
25-
}
3+
// Account data layout, matching the program's `Offer` struct.
4+
export const offerDecoder = getStructDecoder([
5+
['id', getU64Decoder()],
6+
['maker', getAddressDecoder()],
7+
['token_mint_a', getAddressDecoder()],
8+
['token_mint_b', getAddressDecoder()],
9+
['token_b_wanted_amount', getU64Decoder()],
10+
['bump', getU8Decoder()],
11+
]);

tokens/escrow/pinocchio/tests/instruction.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
1-
import { AccountRole, type Address, type KeyPairSigner } from '@solana/kit';
1+
import {
2+
AccountRole,
3+
type Address,
4+
getStructEncoder,
5+
getU8Encoder,
6+
getU64Encoder,
7+
type KeyPairSigner,
8+
} from '@solana/kit';
29
import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
310
import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
4-
import * as borsh from 'borsh';
511

612
enum EscrowInstruction {
713
MakeOffer = 0,
814
TakeOffer = 1,
915
RefundOffer = 2,
1016
}
1117

12-
// Unlike the native example, the Pinocchio program receives the offer PDA bump
13-
// in the instruction data (and stores it) instead of deriving it on-chain.
14-
const MakeOfferSchema = {
15-
struct: {
16-
instruction: 'u8',
17-
id: 'u64',
18-
token_a_offered_amount: 'u64',
19-
token_b_wanted_amount: 'u64',
20-
bump: 'u8',
21-
},
22-
};
18+
// Instruction data layout for the MakeOffer discriminator. Unlike the native
19+
// example, the Pinocchio program receives the offer PDA bump in the instruction
20+
// data (and stores it) instead of deriving it on-chain.
21+
const makeOfferEncoder = getStructEncoder([
22+
['instruction', getU8Encoder()],
23+
['id', getU64Encoder()],
24+
['token_a_offered_amount', getU64Encoder()],
25+
['token_b_wanted_amount', getU64Encoder()],
26+
['bump', getU8Encoder()],
27+
]);
2328

24-
const TakeOfferSchema = {
25-
struct: {
26-
instruction: 'u8',
27-
},
28-
};
29+
// Instruction data layout for the TakeOffer discriminator, which takes no args.
30+
const takeOfferEncoder = getStructEncoder([['instruction', getU8Encoder()]]);
2931

30-
const RefundOfferSchema = {
31-
struct: {
32-
instruction: 'u8',
33-
},
34-
};
32+
// Instruction data layout for the RefundOffer discriminator, which takes no args.
33+
const refundOfferEncoder = getStructEncoder([['instruction', getU8Encoder()]]);
3534

3635
export function buildMakeOffer(props: {
3736
id: bigint;
@@ -47,7 +46,7 @@ export function buildMakeOffer(props: {
4746
payer: KeyPairSigner;
4847
programId: Address;
4948
}) {
50-
const data = borsh.serialize(MakeOfferSchema, {
49+
const data = makeOfferEncoder.encode({
5150
instruction: EscrowInstruction.MakeOffer,
5251
id: props.id,
5352
token_a_offered_amount: props.token_a_offered_amount,
@@ -86,7 +85,7 @@ export function buildTakeOffer(props: {
8685
payer: KeyPairSigner;
8786
programId: Address;
8887
}) {
89-
const data = borsh.serialize(TakeOfferSchema, {
88+
const data = takeOfferEncoder.encode({
9089
instruction: EscrowInstruction.TakeOffer,
9190
});
9291

@@ -119,7 +118,7 @@ export function buildRefundOffer(props: {
119118
maker: KeyPairSigner;
120119
programId: Address;
121120
}) {
122-
const data = borsh.serialize(RefundOfferSchema, {
121+
const data = refundOfferEncoder.encode({
123122
instruction: EscrowInstruction.RefundOffer,
124123
});
125124

0 commit comments

Comments
 (0)